| | |
| | |
| | |
| |
|
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | package json |
| |
|
| | import ( |
| | "bytes" |
| | "cmp" |
| | "encoding" |
| | "encoding/base64" |
| | "fmt" |
| | "math" |
| | "reflect" |
| | "slices" |
| | "strconv" |
| | "strings" |
| | "sync" |
| | "unicode" |
| | "unicode/utf8" |
| | _ "unsafe" |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func Marshal(v any) ([]byte, error) { |
| | e := newEncodeState() |
| | defer encodeStatePool.Put(e) |
| |
|
| | err := e.marshal(v, encOpts{escapeHTML: true}) |
| | if err != nil { |
| | return nil, err |
| | } |
| | buf := append([]byte(nil), e.Bytes()...) |
| |
|
| | return buf, nil |
| | } |
| |
|
| | |
| | |
| | |
| | func MarshalIndent(v any, prefix, indent string) ([]byte, error) { |
| | b, err := Marshal(v) |
| | if err != nil { |
| | return nil, err |
| | } |
| | b2 := make([]byte, 0, indentGrowthFactor*len(b)) |
| | b2, err = appendIndent(b2, b, prefix, indent) |
| | if err != nil { |
| | return nil, err |
| | } |
| | return b2, nil |
| | } |
| |
|
| | |
| | |
| | type Marshaler interface { |
| | MarshalJSON() ([]byte, error) |
| | } |
| |
|
| | |
| | |
| | type UnsupportedTypeError struct { |
| | Type reflect.Type |
| | } |
| |
|
| | func (e *UnsupportedTypeError) Error() string { |
| | return "json: unsupported type: " + e.Type.String() |
| | } |
| |
|
| | |
| | |
| | type UnsupportedValueError struct { |
| | Value reflect.Value |
| | Str string |
| | } |
| |
|
| | func (e *UnsupportedValueError) Error() string { |
| | return "json: unsupported value: " + e.Str |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | type InvalidUTF8Error struct { |
| | S string |
| | } |
| |
|
| | func (e *InvalidUTF8Error) Error() string { |
| | return "json: invalid UTF-8 in string: " + strconv.Quote(e.S) |
| | } |
| |
|
| | |
| | |
| | type MarshalerError struct { |
| | Type reflect.Type |
| | Err error |
| | sourceFunc string |
| | } |
| |
|
| | func (e *MarshalerError) Error() string { |
| | srcFunc := e.sourceFunc |
| | if srcFunc == "" { |
| | srcFunc = "MarshalJSON" |
| | } |
| | return "json: error calling " + srcFunc + |
| | " for type " + e.Type.String() + |
| | ": " + e.Err.Error() |
| | } |
| |
|
| | |
| | func (e *MarshalerError) Unwrap() error { return e.Err } |
| |
|
| | const hex = "0123456789abcdef" |
| |
|
| | |
| | type encodeState struct { |
| | bytes.Buffer |
| |
|
| | |
| | |
| | |
| | |
| | |
| | ptrLevel uint |
| | ptrSeen map[any]struct{} |
| | } |
| |
|
| | const startDetectingCyclesAfter = 1000 |
| |
|
| | var encodeStatePool sync.Pool |
| |
|
| | func newEncodeState() *encodeState { |
| | if v := encodeStatePool.Get(); v != nil { |
| | e := v.(*encodeState) |
| | e.Reset() |
| | if len(e.ptrSeen) > 0 { |
| | panic("ptrEncoder.encode should have emptied ptrSeen via defers") |
| | } |
| | e.ptrLevel = 0 |
| | return e |
| | } |
| | return &encodeState{ptrSeen: make(map[any]struct{})} |
| | } |
| |
|
| | |
| | |
| | |
| | type jsonError struct{ error } |
| |
|
| | func (e *encodeState) marshal(v any, opts encOpts) (err error) { |
| | defer func() { |
| | if r := recover(); r != nil { |
| | if je, ok := r.(jsonError); ok { |
| | err = je.error |
| | } else { |
| | panic(r) |
| | } |
| | } |
| | }() |
| | e.reflectValue(reflect.ValueOf(v), opts) |
| | return nil |
| | } |
| |
|
| | |
| | func (e *encodeState) error(err error) { |
| | panic(jsonError{err}) |
| | } |
| |
|
| | func isEmptyValue(v reflect.Value) bool { |
| | switch v.Kind() { |
| | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: |
| | return v.Len() == 0 |
| | case reflect.Bool, |
| | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, |
| | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, |
| | reflect.Float32, reflect.Float64, |
| | reflect.Interface, reflect.Pointer: |
| | return v.IsZero() |
| | } |
| | return false |
| | } |
| |
|
| | func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) { |
| | valueEncoder(v)(e, v, opts) |
| | } |
| |
|
| | type encOpts struct { |
| | |
| | quoted bool |
| | |
| | escapeHTML bool |
| | } |
| |
|
| | type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts) |
| |
|
| | var encoderCache sync.Map |
| |
|
| | func valueEncoder(v reflect.Value) encoderFunc { |
| | if !v.IsValid() { |
| | return invalidValueEncoder |
| | } |
| | return typeEncoder(v.Type()) |
| | } |
| |
|
| | func typeEncoder(t reflect.Type) encoderFunc { |
| | if fi, ok := encoderCache.Load(t); ok { |
| | return fi.(encoderFunc) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | indirect := sync.OnceValue(func() encoderFunc { |
| | return newTypeEncoder(t, true) |
| | }) |
| | fi, loaded := encoderCache.LoadOrStore(t, encoderFunc(func(e *encodeState, v reflect.Value, opts encOpts) { |
| | indirect()(e, v, opts) |
| | })) |
| | if loaded { |
| | return fi.(encoderFunc) |
| | } |
| |
|
| | f := indirect() |
| | encoderCache.Store(t, f) |
| | return f |
| | } |
| |
|
| | var ( |
| | marshalerType = reflect.TypeFor[Marshaler]() |
| | textMarshalerType = reflect.TypeFor[encoding.TextMarshaler]() |
| | ) |
| |
|
| | |
| | |
| | func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { |
| | |
| | |
| | |
| | |
| | if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) { |
| | return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false)) |
| | } |
| | if t.Implements(marshalerType) { |
| | return marshalerEncoder |
| | } |
| | if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) { |
| | return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false)) |
| | } |
| | if t.Implements(textMarshalerType) { |
| | return textMarshalerEncoder |
| | } |
| |
|
| | switch t.Kind() { |
| | case reflect.Bool: |
| | return boolEncoder |
| | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| | return intEncoder |
| | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| | return uintEncoder |
| | case reflect.Float32: |
| | return float32Encoder |
| | case reflect.Float64: |
| | return float64Encoder |
| | case reflect.String: |
| | return stringEncoder |
| | case reflect.Interface: |
| | return interfaceEncoder |
| | case reflect.Struct: |
| | return newStructEncoder(t) |
| | case reflect.Map: |
| | return newMapEncoder(t) |
| | case reflect.Slice: |
| | return newSliceEncoder(t) |
| | case reflect.Array: |
| | return newArrayEncoder(t) |
| | case reflect.Pointer: |
| | return newPtrEncoder(t) |
| | default: |
| | return unsupportedTypeEncoder |
| | } |
| | } |
| |
|
| | func invalidValueEncoder(e *encodeState, v reflect.Value, _ encOpts) { |
| | e.WriteString("null") |
| | } |
| |
|
| | func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { |
| | if v.Kind() == reflect.Pointer && v.IsNil() { |
| | e.WriteString("null") |
| | return |
| | } |
| | m, ok := reflect.TypeAssert[Marshaler](v) |
| | if !ok { |
| | e.WriteString("null") |
| | return |
| | } |
| | b, err := m.MarshalJSON() |
| | if err == nil { |
| | e.Grow(len(b)) |
| | out := e.AvailableBuffer() |
| | out, err = appendCompact(out, b, opts.escapeHTML) |
| | e.Buffer.Write(out) |
| | } |
| | if err != nil { |
| | e.error(&MarshalerError{v.Type(), err, "MarshalJSON"}) |
| | } |
| | } |
| |
|
| | func addrMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { |
| | va := v.Addr() |
| | if va.IsNil() { |
| | e.WriteString("null") |
| | return |
| | } |
| | m, _ := reflect.TypeAssert[Marshaler](va) |
| | b, err := m.MarshalJSON() |
| | if err == nil { |
| | e.Grow(len(b)) |
| | out := e.AvailableBuffer() |
| | out, err = appendCompact(out, b, opts.escapeHTML) |
| | e.Buffer.Write(out) |
| | } |
| | if err != nil { |
| | e.error(&MarshalerError{v.Type(), err, "MarshalJSON"}) |
| | } |
| | } |
| |
|
| | func textMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { |
| | if v.Kind() == reflect.Pointer && v.IsNil() { |
| | e.WriteString("null") |
| | return |
| | } |
| | m, ok := reflect.TypeAssert[encoding.TextMarshaler](v) |
| | if !ok { |
| | e.WriteString("null") |
| | return |
| | } |
| | b, err := m.MarshalText() |
| | if err != nil { |
| | e.error(&MarshalerError{v.Type(), err, "MarshalText"}) |
| | } |
| | e.Write(appendString(e.AvailableBuffer(), b, opts.escapeHTML)) |
| | } |
| |
|
| | func addrTextMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { |
| | va := v.Addr() |
| | if va.IsNil() { |
| | e.WriteString("null") |
| | return |
| | } |
| | m, _ := reflect.TypeAssert[encoding.TextMarshaler](va) |
| | b, err := m.MarshalText() |
| | if err != nil { |
| | e.error(&MarshalerError{v.Type(), err, "MarshalText"}) |
| | } |
| | e.Write(appendString(e.AvailableBuffer(), b, opts.escapeHTML)) |
| | } |
| |
|
| | func boolEncoder(e *encodeState, v reflect.Value, opts encOpts) { |
| | b := e.AvailableBuffer() |
| | b = mayAppendQuote(b, opts.quoted) |
| | b = strconv.AppendBool(b, v.Bool()) |
| | b = mayAppendQuote(b, opts.quoted) |
| | e.Write(b) |
| | } |
| |
|
| | func intEncoder(e *encodeState, v reflect.Value, opts encOpts) { |
| | b := e.AvailableBuffer() |
| | b = mayAppendQuote(b, opts.quoted) |
| | b = strconv.AppendInt(b, v.Int(), 10) |
| | b = mayAppendQuote(b, opts.quoted) |
| | e.Write(b) |
| | } |
| |
|
| | func uintEncoder(e *encodeState, v reflect.Value, opts encOpts) { |
| | b := e.AvailableBuffer() |
| | b = mayAppendQuote(b, opts.quoted) |
| | b = strconv.AppendUint(b, v.Uint(), 10) |
| | b = mayAppendQuote(b, opts.quoted) |
| | e.Write(b) |
| | } |
| |
|
| | type floatEncoder int |
| |
|
| | func (bits floatEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { |
| | f := v.Float() |
| | if math.IsInf(f, 0) || math.IsNaN(f) { |
| | e.error(&UnsupportedValueError{v, strconv.FormatFloat(f, 'g', -1, int(bits))}) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | b := e.AvailableBuffer() |
| | b = mayAppendQuote(b, opts.quoted) |
| | abs := math.Abs(f) |
| | fmt := byte('f') |
| | |
| | if abs != 0 { |
| | if bits == 64 && (abs < 1e-6 || abs >= 1e21) || bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) { |
| | fmt = 'e' |
| | } |
| | } |
| | b = strconv.AppendFloat(b, f, fmt, -1, int(bits)) |
| | if fmt == 'e' { |
| | |
| | n := len(b) |
| | if n >= 4 && b[n-4] == 'e' && b[n-3] == '-' && b[n-2] == '0' { |
| | b[n-2] = b[n-1] |
| | b = b[:n-1] |
| | } |
| | } |
| | b = mayAppendQuote(b, opts.quoted) |
| | e.Write(b) |
| | } |
| |
|
| | var ( |
| | float32Encoder = (floatEncoder(32)).encode |
| | float64Encoder = (floatEncoder(64)).encode |
| | ) |
| |
|
| | func stringEncoder(e *encodeState, v reflect.Value, opts encOpts) { |
| | if v.Type() == numberType { |
| | numStr := v.String() |
| | |
| | |
| | if numStr == "" { |
| | numStr = "0" |
| | } |
| | if !isValidNumber(numStr) { |
| | e.error(fmt.Errorf("json: invalid number literal %q", numStr)) |
| | } |
| | b := e.AvailableBuffer() |
| | b = mayAppendQuote(b, opts.quoted) |
| | b = append(b, numStr...) |
| | b = mayAppendQuote(b, opts.quoted) |
| | e.Write(b) |
| | return |
| | } |
| | if opts.quoted { |
| | b := appendString(nil, v.String(), opts.escapeHTML) |
| | e.Write(appendString(e.AvailableBuffer(), b, false)) |
| | } else { |
| | e.Write(appendString(e.AvailableBuffer(), v.String(), opts.escapeHTML)) |
| | } |
| | } |
| |
|
| | func isValidNumber(s string) bool { |
| | |
| | |
| | |
| |
|
| | if s == "" { |
| | return false |
| | } |
| |
|
| | |
| | if s[0] == '-' { |
| | s = s[1:] |
| | if s == "" { |
| | return false |
| | } |
| | } |
| |
|
| | |
| | switch { |
| | default: |
| | return false |
| |
|
| | case s[0] == '0': |
| | s = s[1:] |
| |
|
| | case '1' <= s[0] && s[0] <= '9': |
| | s = s[1:] |
| | for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { |
| | s = s[1:] |
| | } |
| | } |
| |
|
| | |
| | if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' { |
| | s = s[2:] |
| | for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { |
| | s = s[1:] |
| | } |
| | } |
| |
|
| | |
| | |
| | if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { |
| | s = s[1:] |
| | if s[0] == '+' || s[0] == '-' { |
| | s = s[1:] |
| | if s == "" { |
| | return false |
| | } |
| | } |
| | for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { |
| | s = s[1:] |
| | } |
| | } |
| |
|
| | |
| | return s == "" |
| | } |
| |
|
| | func interfaceEncoder(e *encodeState, v reflect.Value, opts encOpts) { |
| | if v.IsNil() { |
| | e.WriteString("null") |
| | return |
| | } |
| | e.reflectValue(v.Elem(), opts) |
| | } |
| |
|
| | func unsupportedTypeEncoder(e *encodeState, v reflect.Value, _ encOpts) { |
| | e.error(&UnsupportedTypeError{v.Type()}) |
| | } |
| |
|
| | type structEncoder struct { |
| | fields structFields |
| | } |
| |
|
| | type structFields struct { |
| | list []field |
| | byExactName map[string]*field |
| | byFoldedName map[string]*field |
| | } |
| |
|
| | func (se structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { |
| | next := byte('{') |
| | FieldLoop: |
| | for i := range se.fields.list { |
| | f := &se.fields.list[i] |
| |
|
| | |
| | fv := v |
| | for _, i := range f.index { |
| | if fv.Kind() == reflect.Pointer { |
| | if fv.IsNil() { |
| | continue FieldLoop |
| | } |
| | fv = fv.Elem() |
| | } |
| | fv = fv.Field(i) |
| | } |
| |
|
| | if (f.omitEmpty && isEmptyValue(fv)) || |
| | (f.omitZero && (f.isZero == nil && fv.IsZero() || (f.isZero != nil && f.isZero(fv)))) { |
| | continue |
| | } |
| | e.WriteByte(next) |
| | next = ',' |
| | if opts.escapeHTML { |
| | e.WriteString(f.nameEscHTML) |
| | } else { |
| | e.WriteString(f.nameNonEsc) |
| | } |
| | opts.quoted = f.quoted |
| | f.encoder(e, fv, opts) |
| | } |
| | if next == '{' { |
| | e.WriteString("{}") |
| | } else { |
| | e.WriteByte('}') |
| | } |
| | } |
| |
|
| | func newStructEncoder(t reflect.Type) encoderFunc { |
| | se := structEncoder{fields: cachedTypeFields(t)} |
| | return se.encode |
| | } |
| |
|
| | type mapEncoder struct { |
| | elemEnc encoderFunc |
| | } |
| |
|
| | func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { |
| | if v.IsNil() { |
| | e.WriteString("null") |
| | return |
| | } |
| | if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter { |
| | |
| | |
| | ptr := v.UnsafePointer() |
| | if _, ok := e.ptrSeen[ptr]; ok { |
| | e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())}) |
| | } |
| | e.ptrSeen[ptr] = struct{}{} |
| | defer delete(e.ptrSeen, ptr) |
| | } |
| | e.WriteByte('{') |
| |
|
| | |
| | var ( |
| | sv = make([]reflectWithString, v.Len()) |
| | mi = v.MapRange() |
| | err error |
| | ) |
| | for i := 0; mi.Next(); i++ { |
| | if sv[i].ks, err = resolveKeyName(mi.Key()); err != nil { |
| | e.error(fmt.Errorf("json: encoding error for type %q: %q", v.Type().String(), err.Error())) |
| | } |
| | sv[i].v = mi.Value() |
| | } |
| | slices.SortFunc(sv, func(i, j reflectWithString) int { |
| | return strings.Compare(i.ks, j.ks) |
| | }) |
| |
|
| | for i, kv := range sv { |
| | if i > 0 { |
| | e.WriteByte(',') |
| | } |
| | e.Write(appendString(e.AvailableBuffer(), kv.ks, opts.escapeHTML)) |
| | e.WriteByte(':') |
| | me.elemEnc(e, kv.v, opts) |
| | } |
| | e.WriteByte('}') |
| | e.ptrLevel-- |
| | } |
| |
|
| | func newMapEncoder(t reflect.Type) encoderFunc { |
| | switch t.Key().Kind() { |
| | case reflect.String, |
| | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, |
| | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| | default: |
| | if !t.Key().Implements(textMarshalerType) { |
| | return unsupportedTypeEncoder |
| | } |
| | } |
| | me := mapEncoder{typeEncoder(t.Elem())} |
| | return me.encode |
| | } |
| |
|
| | func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) { |
| | if v.IsNil() { |
| | e.WriteString("null") |
| | return |
| | } |
| |
|
| | s := v.Bytes() |
| | b := e.AvailableBuffer() |
| | b = append(b, '"') |
| | b = base64.StdEncoding.AppendEncode(b, s) |
| | b = append(b, '"') |
| | e.Write(b) |
| | } |
| |
|
| | |
| | type sliceEncoder struct { |
| | arrayEnc encoderFunc |
| | } |
| |
|
| | func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { |
| | if v.IsNil() { |
| | e.WriteString("null") |
| | return |
| | } |
| | if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter { |
| | |
| | |
| | |
| | |
| | ptr := struct { |
| | ptr any |
| | len int |
| | }{v.UnsafePointer(), v.Len()} |
| | if _, ok := e.ptrSeen[ptr]; ok { |
| | e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())}) |
| | } |
| | e.ptrSeen[ptr] = struct{}{} |
| | defer delete(e.ptrSeen, ptr) |
| | } |
| | se.arrayEnc(e, v, opts) |
| | e.ptrLevel-- |
| | } |
| |
|
| | func newSliceEncoder(t reflect.Type) encoderFunc { |
| | |
| | if t.Elem().Kind() == reflect.Uint8 { |
| | p := reflect.PointerTo(t.Elem()) |
| | if !p.Implements(marshalerType) && !p.Implements(textMarshalerType) { |
| | return encodeByteSlice |
| | } |
| | } |
| | enc := sliceEncoder{newArrayEncoder(t)} |
| | return enc.encode |
| | } |
| |
|
| | type arrayEncoder struct { |
| | elemEnc encoderFunc |
| | } |
| |
|
| | func (ae arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { |
| | e.WriteByte('[') |
| | n := v.Len() |
| | for i := 0; i < n; i++ { |
| | if i > 0 { |
| | e.WriteByte(',') |
| | } |
| | ae.elemEnc(e, v.Index(i), opts) |
| | } |
| | e.WriteByte(']') |
| | } |
| |
|
| | func newArrayEncoder(t reflect.Type) encoderFunc { |
| | enc := arrayEncoder{typeEncoder(t.Elem())} |
| | return enc.encode |
| | } |
| |
|
| | type ptrEncoder struct { |
| | elemEnc encoderFunc |
| | } |
| |
|
| | func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { |
| | if v.IsNil() { |
| | e.WriteString("null") |
| | return |
| | } |
| | if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter { |
| | |
| | |
| | ptr := v.Interface() |
| | if _, ok := e.ptrSeen[ptr]; ok { |
| | e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())}) |
| | } |
| | e.ptrSeen[ptr] = struct{}{} |
| | defer delete(e.ptrSeen, ptr) |
| | } |
| | pe.elemEnc(e, v.Elem(), opts) |
| | e.ptrLevel-- |
| | } |
| |
|
| | func newPtrEncoder(t reflect.Type) encoderFunc { |
| | enc := ptrEncoder{typeEncoder(t.Elem())} |
| | return enc.encode |
| | } |
| |
|
| | type condAddrEncoder struct { |
| | canAddrEnc, elseEnc encoderFunc |
| | } |
| |
|
| | func (ce condAddrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { |
| | if v.CanAddr() { |
| | ce.canAddrEnc(e, v, opts) |
| | } else { |
| | ce.elseEnc(e, v, opts) |
| | } |
| | } |
| |
|
| | |
| | |
| | func newCondAddrEncoder(canAddrEnc, elseEnc encoderFunc) encoderFunc { |
| | enc := condAddrEncoder{canAddrEnc: canAddrEnc, elseEnc: elseEnc} |
| | return enc.encode |
| | } |
| |
|
| | func isValidTag(s string) bool { |
| | if s == "" { |
| | return false |
| | } |
| | for _, c := range s { |
| | switch { |
| | case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c): |
| | |
| | |
| | |
| | case !unicode.IsLetter(c) && !unicode.IsDigit(c): |
| | return false |
| | } |
| | } |
| | return true |
| | } |
| |
|
| | func typeByIndex(t reflect.Type, index []int) reflect.Type { |
| | for _, i := range index { |
| | if t.Kind() == reflect.Pointer { |
| | t = t.Elem() |
| | } |
| | t = t.Field(i).Type |
| | } |
| | return t |
| | } |
| |
|
| | type reflectWithString struct { |
| | v reflect.Value |
| | ks string |
| | } |
| |
|
| | func resolveKeyName(k reflect.Value) (string, error) { |
| | if k.Kind() == reflect.String { |
| | return k.String(), nil |
| | } |
| | if tm, ok := reflect.TypeAssert[encoding.TextMarshaler](k); ok { |
| | if k.Kind() == reflect.Pointer && k.IsNil() { |
| | return "", nil |
| | } |
| | buf, err := tm.MarshalText() |
| | return string(buf), err |
| | } |
| | switch k.Kind() { |
| | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| | return strconv.FormatInt(k.Int(), 10), nil |
| | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| | return strconv.FormatUint(k.Uint(), 10), nil |
| | } |
| | panic("unexpected map key type") |
| | } |
| |
|
| | func appendString[Bytes []byte | string](dst []byte, src Bytes, escapeHTML bool) []byte { |
| | dst = append(dst, '"') |
| | start := 0 |
| | for i := 0; i < len(src); { |
| | if b := src[i]; b < utf8.RuneSelf { |
| | if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) { |
| | i++ |
| | continue |
| | } |
| | dst = append(dst, src[start:i]...) |
| | switch b { |
| | case '\\', '"': |
| | dst = append(dst, '\\', b) |
| | case '\b': |
| | dst = append(dst, '\\', 'b') |
| | case '\f': |
| | dst = append(dst, '\\', 'f') |
| | case '\n': |
| | dst = append(dst, '\\', 'n') |
| | case '\r': |
| | dst = append(dst, '\\', 'r') |
| | case '\t': |
| | dst = append(dst, '\\', 't') |
| | default: |
| | |
| | |
| | |
| | |
| | |
| | dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) |
| | } |
| | i++ |
| | start = i |
| | continue |
| | } |
| | |
| | |
| | |
| | |
| | n := min(len(src)-i, utf8.UTFMax) |
| | c, size := utf8.DecodeRuneInString(string(src[i : i+n])) |
| | if c == utf8.RuneError && size == 1 { |
| | dst = append(dst, src[start:i]...) |
| | dst = append(dst, `\ufffd`...) |
| | i += size |
| | start = i |
| | continue |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if c == '\u2028' || c == '\u2029' { |
| | dst = append(dst, src[start:i]...) |
| | dst = append(dst, '\\', 'u', '2', '0', '2', hex[c&0xF]) |
| | i += size |
| | start = i |
| | continue |
| | } |
| | i += size |
| | } |
| | dst = append(dst, src[start:]...) |
| | dst = append(dst, '"') |
| | return dst |
| | } |
| |
|
| | |
| | type field struct { |
| | name string |
| | nameBytes []byte |
| |
|
| | nameNonEsc string |
| | nameEscHTML string |
| |
|
| | tag bool |
| | index []int |
| | typ reflect.Type |
| | omitEmpty bool |
| | omitZero bool |
| | isZero func(reflect.Value) bool |
| | quoted bool |
| |
|
| | encoder encoderFunc |
| | } |
| |
|
| | type isZeroer interface { |
| | IsZero() bool |
| | } |
| |
|
| | var isZeroerType = reflect.TypeFor[isZeroer]() |
| |
|
| | func typeFields(t reflect.Type) structFields { |
| | |
| | current := []field{} |
| | next := []field{{typ: t}} |
| |
|
| | |
| | var count, nextCount map[reflect.Type]int |
| |
|
| | |
| | visited := map[reflect.Type]bool{} |
| |
|
| | |
| | var fields []field |
| |
|
| | |
| | var nameEscBuf []byte |
| |
|
| | for len(next) > 0 { |
| | current, next = next, current[:0] |
| | count, nextCount = nextCount, map[reflect.Type]int{} |
| |
|
| | for _, f := range current { |
| | if visited[f.typ] { |
| | continue |
| | } |
| | visited[f.typ] = true |
| |
|
| | |
| | for i := 0; i < f.typ.NumField(); i++ { |
| | sf := f.typ.Field(i) |
| | if sf.Anonymous { |
| | t := sf.Type |
| | if t.Kind() == reflect.Pointer { |
| | t = t.Elem() |
| | } |
| | if !sf.IsExported() && t.Kind() != reflect.Struct { |
| | |
| | continue |
| | } |
| | |
| | |
| | } else if !sf.IsExported() { |
| | |
| | continue |
| | } |
| | tag := sf.Tag.Get("json") |
| | if tag == "-" { |
| | continue |
| | } |
| | name, opts := parseTag(tag) |
| | if !isValidTag(name) { |
| | name = "" |
| | } |
| | index := make([]int, len(f.index)+1) |
| | copy(index, f.index) |
| | index[len(f.index)] = i |
| |
|
| | ft := sf.Type |
| | if ft.Name() == "" && ft.Kind() == reflect.Pointer { |
| | |
| | ft = ft.Elem() |
| | } |
| |
|
| | |
| | quoted := false |
| | if opts.Contains("string") { |
| | switch ft.Kind() { |
| | case reflect.Bool, |
| | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, |
| | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, |
| | reflect.Float32, reflect.Float64, |
| | reflect.String: |
| | quoted = true |
| | } |
| | } |
| |
|
| | |
| | if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { |
| | tagged := name != "" |
| | if name == "" { |
| | name = sf.Name |
| | } |
| | field := field{ |
| | name: name, |
| | tag: tagged, |
| | index: index, |
| | typ: ft, |
| | omitEmpty: opts.Contains("omitempty"), |
| | omitZero: opts.Contains("omitzero"), |
| | quoted: quoted, |
| | } |
| | field.nameBytes = []byte(field.name) |
| |
|
| | |
| | nameEscBuf = appendHTMLEscape(nameEscBuf[:0], field.nameBytes) |
| | field.nameEscHTML = `"` + string(nameEscBuf) + `":` |
| | field.nameNonEsc = `"` + field.name + `":` |
| |
|
| | if field.omitZero { |
| | t := sf.Type |
| | |
| | switch { |
| | case t.Kind() == reflect.Interface && t.Implements(isZeroerType): |
| | field.isZero = func(v reflect.Value) bool { |
| | |
| | |
| | return v.IsNil() || |
| | (v.Elem().Kind() == reflect.Pointer && v.Elem().IsNil()) || |
| | v.Interface().(isZeroer).IsZero() |
| | } |
| | case t.Kind() == reflect.Pointer && t.Implements(isZeroerType): |
| | field.isZero = func(v reflect.Value) bool { |
| | |
| | return v.IsNil() || v.Interface().(isZeroer).IsZero() |
| | } |
| | case t.Implements(isZeroerType): |
| | field.isZero = func(v reflect.Value) bool { |
| | return v.Interface().(isZeroer).IsZero() |
| | } |
| | case reflect.PointerTo(t).Implements(isZeroerType): |
| | field.isZero = func(v reflect.Value) bool { |
| | if !v.CanAddr() { |
| | |
| | v2 := reflect.New(v.Type()).Elem() |
| | v2.Set(v) |
| | v = v2 |
| | } |
| | return v.Addr().Interface().(isZeroer).IsZero() |
| | } |
| | } |
| | } |
| |
|
| | fields = append(fields, field) |
| | if count[f.typ] > 1 { |
| | |
| | |
| | |
| | |
| | fields = append(fields, fields[len(fields)-1]) |
| | } |
| | continue |
| | } |
| |
|
| | |
| | nextCount[ft]++ |
| | if nextCount[ft] == 1 { |
| | next = append(next, field{name: ft.Name(), index: index, typ: ft}) |
| | } |
| | } |
| | } |
| | } |
| |
|
| | slices.SortFunc(fields, func(a, b field) int { |
| | |
| | |
| | |
| | if c := strings.Compare(a.name, b.name); c != 0 { |
| | return c |
| | } |
| | if c := cmp.Compare(len(a.index), len(b.index)); c != 0 { |
| | return c |
| | } |
| | if a.tag != b.tag { |
| | if a.tag { |
| | return -1 |
| | } |
| | return +1 |
| | } |
| | return slices.Compare(a.index, b.index) |
| | }) |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | |
| | out := fields[:0] |
| | for advance, i := 0, 0; i < len(fields); i += advance { |
| | |
| | |
| | fi := fields[i] |
| | name := fi.name |
| | for advance = 1; i+advance < len(fields); advance++ { |
| | fj := fields[i+advance] |
| | if fj.name != name { |
| | break |
| | } |
| | } |
| | if advance == 1 { |
| | out = append(out, fi) |
| | continue |
| | } |
| | dominant, ok := dominantField(fields[i : i+advance]) |
| | if ok { |
| | out = append(out, dominant) |
| | } |
| | } |
| |
|
| | fields = out |
| | slices.SortFunc(fields, func(i, j field) int { |
| | return slices.Compare(i.index, j.index) |
| | }) |
| |
|
| | for i := range fields { |
| | f := &fields[i] |
| | f.encoder = typeEncoder(typeByIndex(t, f.index)) |
| | } |
| | exactNameIndex := make(map[string]*field, len(fields)) |
| | foldedNameIndex := make(map[string]*field, len(fields)) |
| | for i, field := range fields { |
| | exactNameIndex[field.name] = &fields[i] |
| | |
| | if _, ok := foldedNameIndex[string(foldName(field.nameBytes))]; !ok { |
| | foldedNameIndex[string(foldName(field.nameBytes))] = &fields[i] |
| | } |
| | } |
| | return structFields{fields, exactNameIndex, foldedNameIndex} |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | func dominantField(fields []field) (field, bool) { |
| | |
| | |
| | |
| | if len(fields) > 1 && len(fields[0].index) == len(fields[1].index) && fields[0].tag == fields[1].tag { |
| | return field{}, false |
| | } |
| | return fields[0], true |
| | } |
| |
|
| | var fieldCache sync.Map |
| |
|
| | |
| | func cachedTypeFields(t reflect.Type) structFields { |
| | if f, ok := fieldCache.Load(t); ok { |
| | return f.(structFields) |
| | } |
| | f, _ := fieldCache.LoadOrStore(t, typeFields(t)) |
| | return f.(structFields) |
| | } |
| |
|
| | func mayAppendQuote(b []byte, quoted bool) []byte { |
| | if quoted { |
| | b = append(b, '"') |
| | } |
| | return b |
| | } |
| |
|