55package binding
66
77import (
8+ "io"
9+ "net/http/httptest"
810 "testing"
11+ "time"
12+ "unsafe"
913
14+ "github.com/gin-gonic/gin/codec/json"
15+ "github.com/gin-gonic/gin/render"
16+ jsoniter "github.com/json-iterator/go"
17+ "github.com/modern-go/reflect2"
1018 "github.com/stretchr/testify/assert"
1119 "github.com/stretchr/testify/require"
1220)
@@ -28,3 +36,181 @@ func TestJSONBindingBindBodyMap(t *testing.T) {
2836 assert .Equal (t , "FOO" , s ["foo" ])
2937 assert .Equal (t , "world" , s ["hello" ])
3038}
39+
40+ func TestCustomJsonCodec (t * testing.T ) {
41+ // Restore json encoding configuration after testing
42+ oldMarshal := json .API
43+ defer func () {
44+ json .API = oldMarshal
45+ }()
46+ // Custom json api
47+ json .API = customJsonApi {}
48+
49+ // test decode json
50+ obj := customReq {}
51+ err := jsonBinding {}.BindBody ([]byte (`{"time_empty":null,"time_struct": "2001-12-05 10:01:02.345","time_nil":null,"time_pointer":"2002-12-05 10:01:02.345"}` ), & obj )
52+ require .NoError (t , err )
53+ assert .Equal (t , zeroTime , obj .TimeEmpty )
54+ assert .Equal (t , time .Date (2001 , 12 , 5 , 10 , 1 , 2 , 345000000 , time .Local ), obj .TimeStruct )
55+ assert .Nil (t , obj .TimeNil )
56+ assert .Equal (t , time .Date (2002 , 12 , 5 , 10 , 1 , 2 , 345000000 , time .Local ), * obj .TimePointer )
57+ // test encode json
58+ w := httptest .NewRecorder ()
59+ err2 := (render.PureJSON {Data : obj }).Render (w )
60+ require .NoError (t , err2 )
61+ assert .JSONEq (t , "{\" time_empty\" :null,\" time_struct\" :\" 2001-12-05 10:01:02.345\" ,\" time_nil\" :null,\" time_pointer\" :\" 2002-12-05 10:01:02.345\" }\n " , w .Body .String ())
62+ assert .Equal (t , "application/json; charset=utf-8" , w .Header ().Get ("Content-Type" ))
63+ }
64+
65+ type customReq struct {
66+ TimeEmpty time.Time `json:"time_empty"`
67+ TimeStruct time.Time `json:"time_struct"`
68+ TimeNil * time.Time `json:"time_nil"`
69+ TimePointer * time.Time `json:"time_pointer"`
70+ }
71+
72+ var customConfig = jsoniter.Config {
73+ EscapeHTML : true ,
74+ SortMapKeys : true ,
75+ ValidateJsonRawMessage : true ,
76+ }.Froze ()
77+
78+ func init () {
79+ customConfig .RegisterExtension (& TimeEx {})
80+ customConfig .RegisterExtension (& TimePointerEx {})
81+ }
82+
83+ type customJsonApi struct {}
84+
85+ func (j customJsonApi ) Marshal (v any ) ([]byte , error ) {
86+ return customConfig .Marshal (v )
87+ }
88+
89+ func (j customJsonApi ) Unmarshal (data []byte , v any ) error {
90+ return customConfig .Unmarshal (data , v )
91+ }
92+
93+ func (j customJsonApi ) MarshalIndent (v any , prefix , indent string ) ([]byte , error ) {
94+ return customConfig .MarshalIndent (v , prefix , indent )
95+ }
96+
97+ func (j customJsonApi ) NewEncoder (writer io.Writer ) json.Encoder {
98+ return customConfig .NewEncoder (writer )
99+ }
100+
101+ func (j customJsonApi ) NewDecoder (reader io.Reader ) json.Decoder {
102+ return customConfig .NewDecoder (reader )
103+ }
104+
105+ // region Time Extension
106+
107+ var (
108+ zeroTime = time.Time {}
109+ timeType = reflect2 .TypeOfPtr ((* time .Time )(nil )).Elem ()
110+ defaultTimeCodec = & timeCodec {}
111+ )
112+
113+ type TimeEx struct {
114+ jsoniter.DummyExtension
115+ }
116+
117+ func (te * TimeEx ) CreateDecoder (typ reflect2.Type ) jsoniter.ValDecoder {
118+ if typ == timeType {
119+ return defaultTimeCodec
120+ }
121+ return nil
122+ }
123+
124+ func (te * TimeEx ) CreateEncoder (typ reflect2.Type ) jsoniter.ValEncoder {
125+ if typ == timeType {
126+ return defaultTimeCodec
127+ }
128+ return nil
129+ }
130+
131+ type timeCodec struct {}
132+
133+ func (tc timeCodec ) IsEmpty (ptr unsafe.Pointer ) bool {
134+ t := * ((* time .Time )(ptr ))
135+ return t .Equal (zeroTime )
136+ }
137+
138+ func (tc timeCodec ) Encode (ptr unsafe.Pointer , stream * jsoniter.Stream ) {
139+ t := * ((* time .Time )(ptr ))
140+ if t .Equal (zeroTime ) {
141+ stream .WriteNil ()
142+ return
143+ }
144+ stream .WriteString (t .In (time .Local ).Format ("2006-01-02 15:04:05.000" ))
145+ }
146+
147+ func (tc timeCodec ) Decode (ptr unsafe.Pointer , iter * jsoniter.Iterator ) {
148+ ts := iter .ReadString ()
149+ if len (ts ) == 0 {
150+ * ((* time .Time )(ptr )) = zeroTime
151+ return
152+ }
153+ t , err := time .ParseInLocation ("2006-01-02 15:04:05.000" , ts , time .Local )
154+ if err != nil {
155+ panic (err )
156+ }
157+ * ((* time .Time )(ptr )) = t
158+ }
159+
160+ // endregion
161+
162+ // region *Time Extension
163+
164+ var (
165+ timePointerType = reflect2 .TypeOfPtr ((* * time .Time )(nil )).Elem ()
166+ defaultTimePointerCodec = & timePointerCodec {}
167+ )
168+
169+ type TimePointerEx struct {
170+ jsoniter.DummyExtension
171+ }
172+
173+ func (tpe * TimePointerEx ) CreateDecoder (typ reflect2.Type ) jsoniter.ValDecoder {
174+ if typ == timePointerType {
175+ return defaultTimePointerCodec
176+ }
177+ return nil
178+ }
179+
180+ func (tpe * TimePointerEx ) CreateEncoder (typ reflect2.Type ) jsoniter.ValEncoder {
181+ if typ == timePointerType {
182+ return defaultTimePointerCodec
183+ }
184+ return nil
185+ }
186+
187+ type timePointerCodec struct {}
188+
189+ func (tpc timePointerCodec ) IsEmpty (ptr unsafe.Pointer ) bool {
190+ t := * ((* * time .Time )(ptr ))
191+ return t == nil || (* t ).Equal (zeroTime )
192+ }
193+
194+ func (tpc timePointerCodec ) Encode (ptr unsafe.Pointer , stream * jsoniter.Stream ) {
195+ t := * ((* * time .Time )(ptr ))
196+ if t == nil || (* t ).Equal (zeroTime ) {
197+ stream .WriteNil ()
198+ return
199+ }
200+ stream .WriteString (t .In (time .Local ).Format ("2006-01-02 15:04:05.000" ))
201+ }
202+
203+ func (tpc timePointerCodec ) Decode (ptr unsafe.Pointer , iter * jsoniter.Iterator ) {
204+ ts := iter .ReadString ()
205+ if len (ts ) == 0 {
206+ * ((* * time .Time )(ptr )) = nil
207+ return
208+ }
209+ t , err := time .ParseInLocation ("2006-01-02 15:04:05.000" , ts , time .Local )
210+ if err != nil {
211+ panic (err )
212+ }
213+ * ((* * time .Time )(ptr )) = & t
214+ }
215+
216+ // endregion
0 commit comments