github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/ginx/jsoni.go (about) 1 package ginx 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/http" 8 9 "github.com/bingoohuang/gg/pkg/jsoni" 10 "github.com/bingoohuang/gg/pkg/jsoni/extra" 11 "github.com/bingoohuang/gg/pkg/strcase" 12 "github.com/gin-gonic/gin" 13 "github.com/gin-gonic/gin/binding" 14 ) 15 16 // ShouldBind checks the Content-Type to select a binding engine automatically, 17 // Depending on the "Content-Type" header different bindings are used: 18 // 19 // "application/json" --> JSON binding 20 // "application/xml" --> XML binding 21 // 22 // otherwise --> returns an error 23 // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. 24 // It decodes the json payload into the struct specified as a pointer. 25 // Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid. 26 func ShouldBind(c *gin.Context, obj interface{}) error { 27 var b binding.Binding 28 switch c.ContentType() { 29 case binding.MIMEJSON: 30 b = JSONBind 31 default: 32 b = binding.Default(c.Request.Method, c.ContentType()) 33 } 34 return c.ShouldBindWith(obj, b) 35 } 36 37 var JSONBind = jsoniBinding{} 38 39 type jsoniBinding struct{} 40 41 func (jsoniBinding) Name() string { return "json" } 42 43 func (jsoniBinding) Bind(req *http.Request, obj interface{}) error { 44 if req == nil || req.Body == nil { 45 return fmt.Errorf("invalid request") 46 } 47 return decodeJSON(req.Body, obj) 48 } 49 50 func (jsoniBinding) BindBody(body []byte, obj interface{}) error { 51 return decodeJSON(bytes.NewReader(body), obj) 52 } 53 54 // JsoniConfig tries to be 100% compatible with standard library behavior 55 var JsoniConfig = jsoni.Config{ 56 EscapeHTML: true, 57 SortMapKeys: true, 58 ValidateJsonRawMessage: true, 59 Int64AsString: true, 60 }.Froze() 61 62 func init() { 63 JsoniConfig.RegisterExtension(&extra.NamingStrategyExtension{Translate: strcase.ToCamelLower}) 64 } 65 66 func decodeJSON(r io.Reader, obj interface{}) error { 67 if err := JsoniConfig.NewDecoder(r).Decode(nil, obj); err != nil { 68 return err 69 } 70 return validate(obj) 71 } 72 73 func validate(obj interface{}) error { 74 validator := binding.Validator 75 if validator == nil { 76 return nil 77 } 78 return validator.ValidateStruct(obj) 79 } 80 81 // JSONRender contains the given interface object. 82 type JSONRender struct { 83 Data interface{} 84 JsoniAPI jsoni.API 85 } 86 87 var jsonContentType = []string{"application/json; charset=utf-8"} 88 89 // Render (JSON) writes data with custom ContentType. 90 func (r JSONRender) Render(w http.ResponseWriter) (err error) { 91 return WriteJSONOptions(w, r.Data, WithJsoniAPI(r.JsoniAPI)) 92 } 93 94 // WriteContentType (JSON) writes JSON ContentType. 95 func (r JSONRender) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } 96 97 type WriteJSONConfig struct { 98 JsoniAPI jsoni.API 99 } 100 101 type WriteJSONConfigFn func(*WriteJSONConfig) 102 103 func WithJsoniAPI(api jsoni.API) WriteJSONConfigFn { 104 return func(o *WriteJSONConfig) { 105 o.JsoniAPI = api 106 } 107 } 108 109 // WriteJSONOptions marshals the given interface object and writes it with custom ContentType. 110 func WriteJSONOptions(w http.ResponseWriter, obj interface{}, fns ...WriteJSONConfigFn) error { 111 options := &WriteJSONConfig{} 112 for _, fn := range fns { 113 fn(options) 114 } 115 116 if options.JsoniAPI == nil { 117 options.JsoniAPI = JsoniConfig 118 } 119 120 writeContentType(w, jsonContentType) 121 122 data, err := options.JsoniAPI.Marshal(nil, obj) 123 if err != nil { 124 return err 125 } 126 _, err = w.Write(data) 127 return err 128 } 129 130 // WriteJSON marshals the given interface object and writes it with custom ContentType. 131 func WriteJSON(w http.ResponseWriter, obj interface{}) error { 132 writeContentType(w, jsonContentType) 133 jsonBytes, err := JsoniConfig.Marshal(nil, obj) 134 if err != nil { 135 return err 136 } 137 _, err = w.Write(jsonBytes) 138 return err 139 } 140 141 func writeContentType(w http.ResponseWriter, value []string) { 142 header := w.Header() 143 if val := header["Content-Type"]; len(val) == 0 { 144 header["Content-Type"] = value 145 } 146 }