package json import ( "bytes" "errors" "math" "strconv" "unsafe" ) type Marshaler interface { MarshalJSON() ([]byte, error) } type Unmarshaler interface { UnmarshalJSON([]byte) error } func Marshal(v any) ([]byte, error) { if v == nil { return []byte("null"), nil } if m, ok := v.(Marshaler); ok { return m.MarshalJSON() } tc := typeCodeOf(v) if tc == nil { return nil, errors.New("json: cannot marshal nil") } dp := dataOf(v) buf := []byte{:0:256} return appendValue(buf, tc, dp) } func Unmarshal(data []byte, v any) error { if v == nil { return errors.New("json: Unmarshal requires non-nil pointer") } if u, ok := v.(Unmarshaler); ok { return u.UnmarshalJSON(data) } elemType, dataPtr := derefPtr(v) if elemType == nil { return errors.New("json: Unmarshal requires pointer argument") } if dataPtr == nil { return errors.New("json: Unmarshal into nil pointer") } pos := skipWS(data, 0) _, err := parseValue(data, pos, elemType, dataPtr) return err } // --- Marshal internals --- func appendValue(buf []byte, typ *_rawType, ptr unsafe.Pointer) ([]byte, error) { // Check Marshaler interface on pointer-to-type. iface := ptrToIface(typ, ptr) if iface != nil { if m, ok := iface.(Marshaler); ok { b, err := m.MarshalJSON() if err != nil { return nil, err } return append(buf, b...), nil } } switch typ.kind() { case jkBool: if *(*bool)(ptr) { return append(buf, "true"...), nil } return append(buf, "false"...), nil case jkInt: return strconv.AppendInt(buf, int64(*(*int32)(ptr)), 10), nil case jkInt8: return strconv.AppendInt(buf, int64(*(*int8)(ptr)), 10), nil case jkInt16: return strconv.AppendInt(buf, int64(*(*int16)(ptr)), 10), nil case jkInt32: return strconv.AppendInt(buf, int64(*(*int32)(ptr)), 10), nil case jkInt64: return strconv.AppendInt(buf, *(*int64)(ptr), 10), nil case jkUint: return strconv.AppendUint(buf, uint64(*(*uint32)(ptr)), 10), nil case jkUint8: return strconv.AppendUint(buf, uint64(*(*uint8)(ptr)), 10), nil case jkUint16: return strconv.AppendUint(buf, uint64(*(*uint16)(ptr)), 10), nil case jkUint32: return strconv.AppendUint(buf, uint64(*(*uint32)(ptr)), 10), nil case jkUint64: return strconv.AppendUint(buf, *(*uint64)(ptr), 10), nil case jkFloat32: f := float64(*(*float32)(ptr)) if math.IsInf(f, 0) || math.IsNaN(f) { return nil, errors.New("json: unsupported float value") } return strconv.AppendFloat(buf, f, 'f', -1, 32), nil case jkFloat64: f := *(*float64)(ptr) if math.IsInf(f, 0) || math.IsNaN(f) { return nil, errors.New("json: unsupported float value") } return strconv.AppendFloat(buf, f, 'f', -1, 64), nil case jkBytes: s := *(*[]byte)(ptr) return appendString(buf, s), nil case jkPointer: elemType := typ.elem() elemPtr := *(*unsafe.Pointer)(ptr) if elemPtr == nil { return append(buf, "null"...), nil } return appendValue(buf, elemType, elemPtr) case jkStruct: return appendStruct(buf, typ, ptr) case jkSlice: return appendSlice(buf, typ, ptr) case jkMap: return nil, errors.New("json: map types not supported") case jkInterface: return nil, errors.New("json: interface{} field values not supported") case jkFunc: return nil, errors.New("json: func types not supported") case jkChan: return nil, errors.New("json: chan types not supported") } return nil, errors.New("json: unsupported type") } func appendStruct(buf []byte, typ *_rawType, ptr unsafe.Pointer) ([]byte, error) { sf := structFieldsFor(typ) buf = append(buf, '{') first := true for i := 0; i < len(sf.params); i++ { p := &sf.params[i] if p.skip { continue } ft := typ.fieldType(i) fp := unsafe.Add(ptr, typ.fieldOffset(i)) if p.omitEmpty && isZeroValue(ft, fp) { continue } if !first { buf = append(buf, ',') } first = false buf = appendString(buf, p.jsonName) buf = append(buf, ':') var err error buf, err = appendValue(buf, ft, fp) if err != nil { return nil, err } } buf = append(buf, '}') return buf, nil } func appendSlice(buf []byte, typ *_rawType, ptr unsafe.Pointer) ([]byte, error) { hdr := (*_sliceHeader)(ptr) if hdr.data == nil { return append(buf, "null"...), nil } elemType := typ.elem() buf = append(buf, '[') for i := 0; i < hdr.len; i++ { if i > 0 { buf = append(buf, ',') } elemPtr := unsafe.Add(hdr.data, elemType.size()*uintptr(i)) var err error buf, err = appendValue(buf, elemType, elemPtr) if err != nil { return nil, err } } buf = append(buf, ']') return buf, nil } func appendString(buf []byte, s []byte) []byte { buf = append(buf, '"') start := 0 for i := 0; i < len(s); i++ { c := s[i] var esc []byte switch c { case '"': esc = []byte(`\"`) case '\\': esc = []byte(`\\`) case '\n': esc = []byte(`\n`) case '\r': esc = []byte(`\r`) case '\t': esc = []byte(`\t`) case '\b': esc = []byte(`\b`) case '\f': esc = []byte(`\f`) default: if c < 0x20 { hc := _hexChars() esc = []byte{'\\', 'u', '0', '0', hc[c>>4], hc[c&0x0f]} } else { continue } } buf = append(buf, s[start:i]...) buf = append(buf, esc...) start = i + 1 } buf = append(buf, s[start:]...) buf = append(buf, '"') return buf } func _hexChars() [16]byte { return [16]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'} } // --- Tag parsing --- type fieldParams struct { jsonName []byte omitEmpty bool skip bool asString bool } type structFields struct { byName map[string]int params []fieldParams } var _fieldCacheMapDone bool var _fieldCacheMap map[uintptr]*structFields func fieldCache() map[uintptr]*structFields { if !_fieldCacheMapDone { _fieldCacheMapDone = true _fieldCacheMap = map[uintptr]*structFields{} } return _fieldCacheMap } func structFieldsFor(typ *_rawType) *structFields { key := uintptr(unsafe.Pointer(typ)) fc := fieldCache() if sf, ok := fc[key]; ok { return sf } u := typ.underlying() n := u.numField() sf := &structFields{ byName: map[string]int{}, params: []fieldParams{:n}, } for i := 0; i < n; i++ { if !u.fieldExported(i) { continue } tag := u.fieldTag(i) name := u.fieldName(i) p := parseJSONTag(tag, name) sf.params[i] = p if !p.skip { sf.byName[string(p.jsonName)] = i } } fc[key] = sf return sf } func parseJSONTag(tag []byte, fieldName []byte) fieldParams { var p fieldParams jsonTag := getJSONTag(tag) if jsonTag == nil { p.jsonName = SnakeCase(fieldName) return p } name, rest, _ := bytes.Cut(jsonTag, ",") if string(name) == "-" && len(rest) == 0 { p.skip = true return p } if len(name) > 0 { p.jsonName = name } else { p.jsonName = SnakeCase(fieldName) } for len(rest) > 0 { var opt []byte opt, rest, _ = bytes.Cut(rest, ",") switch { case string(opt) == "omitempty": p.omitEmpty = true case string(opt) == "string": p.asString = true } } return p } func getJSONTag(tag []byte) []byte { key := []byte(`json:"`) i := bytes.Index(tag, key) if i < 0 { return nil } tag = tag[i+len(key):] j := bytes.IndexByte(tag, '"') if j < 0 { return tag } return tag[:j] } // --- snake_case --- func SnakeCase(s []byte) []byte { if len(s) == 0 { return s } buf := []byte{:0:len(s)+4} wordStart := 0 i := 0 for i < len(s) { if !isUpper(s[i]) { i++ continue } // Start of an uppercase run. runStart := i for i < len(s) && isUpper(s[i]) { i++ } runLen := i - runStart if runStart > wordStart { // Flush lowercase word before the run. if len(buf) > 0 { buf = append(buf, '_') } buf = appendLower(buf, s[wordStart:runStart]) } if i < len(s) && isLower(s[i]) { // Lookahead: last uppercase of run belongs to next word. if runLen > 1 { if len(buf) > 0 { buf = append(buf, '_') } buf = appendLower(buf, s[runStart:i-1]) } // The last uppercase char starts the next word. wordStart = i - 1 } else { // End of string or next char is also uppercase - entire run is one word. if len(buf) > 0 { buf = append(buf, '_') } buf = appendLower(buf, s[runStart:i]) wordStart = i } } if wordStart < len(s) { if len(buf) > 0 { buf = append(buf, '_') } buf = appendLower(buf, s[wordStart:]) } return buf } func isUpper(c byte) bool { return c >= 'A' && c <= 'Z' } func isLower(c byte) bool { return c >= 'a' && c <= 'z' } func appendLower(buf []byte, s []byte) []byte { for _, c := range s { if isUpper(c) { buf = append(buf, c+32) } else { buf = append(buf, c) } } return buf }