Last active
December 25, 2020 15:48
-
-
Save syhily/204ce895614a87e5358a445a738bacf7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package json | |
import ( | |
"bytes" | |
"fmt" | |
"io" | |
"net/http" | |
"strings" | |
"github.com/gin-gonic/gin/binding" | |
) | |
// Return a binding interface for gin, which support the json mapping. | |
func Binding(validator binding.StructValidator) binding.BindingBody { | |
return &jsonBinding{validator} | |
} | |
type jsonBinding struct { | |
validator binding.StructValidator | |
} | |
func (*jsonBinding) Name() string { | |
return "json" | |
} | |
func (b *jsonBinding) Bind(req *http.Request, obj interface{}) error { | |
if req == nil || req.Body == nil { | |
return fmt.Errorf("invalid request") | |
} | |
return decodeJSON(req.Body, obj, b.validator) | |
} | |
func (b *jsonBinding) BindBody(body []byte, obj interface{}) error { | |
return decodeJSON(bytes.NewReader(body), obj, b.validator) | |
} | |
func decodeJSON(r io.Reader, obj interface{}, validator binding.StructValidator) error { | |
decoder := Default.NewDecoder(r) | |
decoder.UseNumber() | |
if err := decoder.Decode(obj); err != nil { | |
return err | |
} | |
if validator != nil { | |
return validator.ValidateStruct(obj) | |
} | |
return nil | |
} | |
// Exposing some useful function for internal usage for JSON. | |
// Decode would mapping the json into the desired struct pointer. | |
func Decode(json string, any interface{}) error { | |
decoder := Default.NewDecoder(strings.NewReader(json)) | |
return decoder.Decode(any) | |
} | |
// Encode would convert the instance into json str. | |
func Encode(any interface{}) (string, error) { | |
builder := strings.Builder{} | |
encoder := Default.NewEncoder(&builder) | |
err := encoder.Encode(any) | |
if err != nil { | |
return "", err | |
} | |
return builder.String(), nil | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package json | |
import ( | |
"reflect" | |
"strconv" | |
"strings" | |
"time" | |
"unsafe" | |
"github.com/json-iterator/go" | |
) | |
// Available time formats. | |
const ( | |
ANSIC = "ansic" | |
UnixDate = "unixdate" | |
RubyDate = "rubydate" | |
RFC822 = "rfc822" | |
RFC822Z = "rfc822z" | |
RFC850 = "rfc850" | |
RFC1123 = "rfc1123" | |
RFC1123Z = "rfc1123z" | |
RFC3339 = "rfc3339" | |
RFC3339Nano = "rfc3339nano" | |
Kitchen = "kitchen" | |
Stamp = "stamp" | |
StampMilli = "stampmilli" | |
StampMicro = "stampmicro" | |
StampNano = "stampnano" | |
Unix = "unix" | |
UnixNano = "unixnano" | |
) | |
// Time locale alias. | |
const ( | |
Local = "local" | |
UTC = "utc" | |
) | |
// Supported tag name definition. | |
const ( | |
tagNameTimeFormat = "time_format" | |
tagNameTimeUTC = "time_utc" | |
tagNameTimeLocation = "time_location" // `time_location` would override the `time_utc`. | |
) | |
var formatDefinition = map[string]string{ | |
ANSIC: time.ANSIC, | |
UnixDate: time.UnixDate, | |
RubyDate: time.RubyDate, | |
RFC822: time.RFC822, | |
RFC822Z: time.RFC822Z, | |
RFC850: time.RFC850, | |
RFC1123: time.RFC1123, | |
RFC1123Z: time.RFC1123Z, | |
RFC3339: time.RFC3339, | |
RFC3339Nano: time.RFC3339Nano, | |
Kitchen: time.Kitchen, | |
Stamp: time.Stamp, | |
StampMilli: time.StampMilli, | |
StampMicro: time.StampMicro, | |
StampNano: time.StampNano, | |
} | |
var localeDefinition = map[string]*time.Location{ | |
Local: time.Local, | |
UTC: time.UTC, | |
} | |
var ( | |
defaultFormat = time.RFC3339 | |
defaultLocale = time.Local | |
) | |
var Default = jsoniter.ConfigCompatibleWithStandardLibrary | |
func init() { | |
Default.RegisterExtension(&CustomTimeExtension{}) | |
} | |
type CustomTimeExtension struct { | |
jsoniter.DummyExtension | |
} | |
// parseTimeFormat Read time format from struct tag. | |
func parseTimeFormat(tag reflect.StructTag) (format string) { | |
format = defaultFormat | |
name := strings.ToLower(tag.Get(tagNameTimeFormat)) | |
if _format, ok := formatDefinition[name]; ok { | |
format = _format | |
} else if name != "" { | |
format = name | |
} | |
return | |
} | |
// parseTimeLocale Read time locale from struct tag. | |
func parseTimeLocale(tag reflect.StructTag) (locale *time.Location, err error) { | |
locale = defaultLocale | |
// Read locale tag. | |
if localeTag := strings.ToLower(tag.Get(tagNameTimeLocation)); localeTag != "" { | |
ok := false | |
if locale, ok = localeDefinition[localeTag]; !ok { | |
locale, err = time.LoadLocation(localeTag) | |
} | |
return | |
} | |
// Fallback to UTC tag. | |
if isUTC, _ := strconv.ParseBool(tag.Get(tagNameTimeUTC)); isUTC { | |
locale = time.UTC | |
} | |
return | |
} | |
func timeEncode(format string, locale *time.Location) func(*time.Time) string { | |
return func(time *time.Time) string { | |
lt := time.In(locale) | |
if format == Unix { | |
return strconv.FormatInt(lt.Unix(), 10) | |
} else if format == UnixNano { | |
return strconv.FormatInt(lt.UnixNano(), 10) | |
} else { | |
return lt.Format(format) | |
} | |
} | |
} | |
func timeDecode(format string, locale *time.Location) func(*jsoniter.Iterator) (*time.Time, error) { | |
return func(iterator *jsoniter.Iterator) (*time.Time, error) { | |
if format == Unix || format == UnixNano { | |
tv := iterator.ReadInt64() | |
if tv == int64(0) { | |
return nil, nil | |
} | |
d := time.Duration(1) | |
if format == UnixNano { | |
d = time.Second | |
} | |
t := time.Unix(tv/int64(d), tv%int64(d)) | |
return &t, nil | |
} else { | |
str := iterator.ReadString() | |
if str != "" { | |
} else { | |
return nil, nil | |
} | |
tmp, err := time.ParseInLocation(format, str, locale) | |
if err != nil { | |
return nil, err | |
} | |
return &tmp, nil | |
} | |
} | |
} | |
func (extension *CustomTimeExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) { | |
for _, binding := range structDescriptor.Fields { | |
var typeErr error | |
var isPtr bool | |
typeName := binding.Field.Type().String() | |
if typeName == "time.Time" { | |
isPtr = false | |
} else if typeName == "*time.Time" { | |
isPtr = true | |
} else { | |
continue | |
} | |
tag := binding.Field.Tag() | |
format := parseTimeFormat(tag) | |
locale, typeErr := parseTimeLocale(tag) | |
binding.Encoder = &funcEncoder{fun: func(ptr unsafe.Pointer, stream *jsoniter.Stream) { | |
if typeErr != nil { | |
stream.Error = typeErr | |
return | |
} | |
var tp *time.Time | |
if isPtr { | |
tpp := (**time.Time)(ptr) | |
tp = *(tpp) | |
} else { | |
tp = (*time.Time)(ptr) | |
} | |
if tp != nil { | |
encode := timeEncode(format, locale) | |
stream.WriteString(encode(tp)) | |
} else { | |
_, _ = stream.Write([]byte("null")) | |
} | |
}} | |
binding.Decoder = &funcDecoder{fun: func(ptr unsafe.Pointer, iterator *jsoniter.Iterator) { | |
if typeErr != nil { | |
iterator.Error = typeErr | |
return | |
} | |
var t *time.Time | |
decode := timeDecode(format, locale) | |
t, iterator.Error = decode(iterator) | |
if isPtr { | |
tpp := (**time.Time)(ptr) | |
*tpp = t | |
} else { | |
tp := (*time.Time)(ptr) | |
if tp != nil && t != nil { | |
*tp = *t | |
} | |
} | |
}} | |
} | |
} | |
type funcDecoder struct { | |
fun jsoniter.DecoderFunc | |
} | |
func (decoder *funcDecoder) Decode(ptr unsafe.Pointer, iterator *jsoniter.Iterator) { | |
decoder.fun(ptr, iterator) | |
} | |
type funcEncoder struct { | |
fun jsoniter.EncoderFunc | |
isEmptyFunc func(ptr unsafe.Pointer) bool | |
} | |
func (encoder *funcEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { | |
encoder.fun(ptr, stream) | |
} | |
func (encoder *funcEncoder) IsEmpty(ptr unsafe.Pointer) bool { | |
if encoder.isEmptyFunc == nil { | |
return false | |
} | |
return encoder.isEmptyFunc(ptr) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment