Skip to content

Instantly share code, notes, and snippets.

@syhily
Last active December 25, 2020 15:48
Show Gist options
  • Save syhily/204ce895614a87e5358a445a738bacf7 to your computer and use it in GitHub Desktop.
Save syhily/204ce895614a87e5358a445a738bacf7 to your computer and use it in GitHub Desktop.
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
}
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