Apply goext specific patches to gojson
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Has been cancelled
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Has been cancelled
This commit is contained in:
194
gojson/encode.go
194
gojson/encode.go
@@ -170,6 +170,32 @@ func Marshal(v any) ([]byte, error) {
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
type IndentOpt struct {
|
||||
Prefix string
|
||||
Indent string
|
||||
}
|
||||
|
||||
// MarshalSafeCollections is like Marshal except it will marshal nil maps and
|
||||
// slices as '{}' and '[]' respectfully instead of 'null'
|
||||
func MarshalSafeCollections(v interface{}, nilSafeSlices bool, nilSafeMaps bool, indent *IndentOpt, filter *string) ([]byte, error) {
|
||||
e := &encodeState{}
|
||||
err := e.marshal(v, encOpts{escapeHTML: true, nilSafeSlices: nilSafeSlices, nilSafeMaps: nilSafeMaps, filter: filter})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := e.Bytes()
|
||||
if indent != nil {
|
||||
var buf bytes.Buffer
|
||||
err = Indent(&buf, b, indent.Prefix, indent.Indent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
} else {
|
||||
return e.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalIndent is like [Marshal] but applies [Indent] to format the output.
|
||||
// Each JSON element in the output will begin on a new line beginning with prefix
|
||||
// followed by one or more copies of indent according to the indentation nesting.
|
||||
@@ -319,7 +345,11 @@ func isEmptyValue(v reflect.Value) bool {
|
||||
}
|
||||
|
||||
func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
|
||||
valueEncoder(v)(e, v, opts)
|
||||
tagkey := "json"
|
||||
if opts.tagkey != nil {
|
||||
tagkey = *opts.tagkey
|
||||
}
|
||||
valueEncoder(v, tagkey)(e, v, opts)
|
||||
}
|
||||
|
||||
type encOpts struct {
|
||||
@@ -327,21 +357,30 @@ type encOpts struct {
|
||||
quoted bool
|
||||
// escapeHTML causes '<', '>', and '&' to be escaped in JSON strings.
|
||||
escapeHTML bool
|
||||
// nilSafeSlices marshals a nil slices into '[]' instead of 'null'
|
||||
nilSafeSlices bool
|
||||
// nilSafeMaps marshals a nil maps '{}' instead of 'null'
|
||||
nilSafeMaps bool
|
||||
// filter matches jsonfilter tag of struct
|
||||
// marshals if no jsonfilter is set or otherwise if jsonfilter has the filter value
|
||||
filter *string
|
||||
// use different tag instead of "json"
|
||||
tagkey *string
|
||||
}
|
||||
|
||||
type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts)
|
||||
|
||||
var encoderCache sync.Map // map[reflect.Type]encoderFunc
|
||||
|
||||
func valueEncoder(v reflect.Value) encoderFunc {
|
||||
func valueEncoder(v reflect.Value, tagkey string) encoderFunc {
|
||||
if !v.IsValid() {
|
||||
return invalidValueEncoder
|
||||
}
|
||||
return typeEncoder(v.Type())
|
||||
return typeEncoder(v.Type(), tagkey)
|
||||
}
|
||||
|
||||
func typeEncoder(t reflect.Type) encoderFunc {
|
||||
if fi, ok := encoderCache.Load(t); ok {
|
||||
func typeEncoder(t reflect.Type, tagkey string) encoderFunc {
|
||||
if fi, ok := encoderCache.Load(TagKeyTypeKey{t, tagkey}); ok {
|
||||
return fi.(encoderFunc)
|
||||
}
|
||||
|
||||
@@ -354,7 +393,7 @@ func typeEncoder(t reflect.Type) encoderFunc {
|
||||
f encoderFunc
|
||||
)
|
||||
wg.Add(1)
|
||||
fi, loaded := encoderCache.LoadOrStore(t, encoderFunc(func(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
fi, loaded := encoderCache.LoadOrStore(TagKeyTypeKey{t, tagkey}, encoderFunc(func(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
wg.Wait()
|
||||
f(e, v, opts)
|
||||
}))
|
||||
@@ -363,9 +402,9 @@ func typeEncoder(t reflect.Type) encoderFunc {
|
||||
}
|
||||
|
||||
// Compute the real encoder and replace the indirect func with it.
|
||||
f = newTypeEncoder(t, true)
|
||||
f = newTypeEncoder(t, true, tagkey)
|
||||
wg.Done()
|
||||
encoderCache.Store(t, f)
|
||||
encoderCache.Store(TagKeyTypeKey{t, tagkey}, f)
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -376,19 +415,19 @@ var (
|
||||
|
||||
// newTypeEncoder constructs an encoderFunc for a type.
|
||||
// The returned encoder only checks CanAddr when allowAddr is true.
|
||||
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
|
||||
func newTypeEncoder(t reflect.Type, allowAddr bool, tagkey string) encoderFunc {
|
||||
// If we have a non-pointer value whose type implements
|
||||
// Marshaler with a value receiver, then we're better off taking
|
||||
// the address of the value - otherwise we end up with an
|
||||
// allocation as we cast the value to an interface.
|
||||
if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(marshalerType) {
|
||||
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
|
||||
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false, tagkey))
|
||||
}
|
||||
if t.Implements(marshalerType) {
|
||||
return marshalerEncoder
|
||||
}
|
||||
if t.Kind() != reflect.Pointer && allowAddr && reflect.PointerTo(t).Implements(textMarshalerType) {
|
||||
return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
|
||||
return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false, tagkey))
|
||||
}
|
||||
if t.Implements(textMarshalerType) {
|
||||
return textMarshalerEncoder
|
||||
@@ -410,15 +449,15 @@ func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
|
||||
case reflect.Interface:
|
||||
return interfaceEncoder
|
||||
case reflect.Struct:
|
||||
return newStructEncoder(t)
|
||||
return newStructEncoder(t, tagkey)
|
||||
case reflect.Map:
|
||||
return newMapEncoder(t)
|
||||
return newMapEncoder(t, tagkey)
|
||||
case reflect.Slice:
|
||||
return newSliceEncoder(t)
|
||||
return newSliceEncoder(t, tagkey)
|
||||
case reflect.Array:
|
||||
return newArrayEncoder(t)
|
||||
return newArrayEncoder(t, tagkey)
|
||||
case reflect.Pointer:
|
||||
return newPtrEncoder(t)
|
||||
return newPtrEncoder(t, tagkey)
|
||||
default:
|
||||
return unsupportedTypeEncoder
|
||||
}
|
||||
@@ -703,6 +742,8 @@ FieldLoop:
|
||||
|
||||
if f.omitEmpty && isEmptyValue(fv) {
|
||||
continue
|
||||
} else if !matchesJSONFilter(f.jsonfilter, opts.filter) {
|
||||
continue
|
||||
}
|
||||
e.WriteByte(next)
|
||||
next = ','
|
||||
@@ -721,8 +762,27 @@ FieldLoop:
|
||||
}
|
||||
}
|
||||
|
||||
func newStructEncoder(t reflect.Type) encoderFunc {
|
||||
se := structEncoder{fields: cachedTypeFields(t)}
|
||||
func matchesJSONFilter(filter jsonfilter, value *string) bool {
|
||||
if len(filter) == 0 {
|
||||
return true // no filter in struct
|
||||
}
|
||||
if value == nil || *value == "" {
|
||||
return false // no filter set, but struct has filter, return false
|
||||
}
|
||||
if len(filter) == 1 && filter[0] == "-" {
|
||||
return false
|
||||
}
|
||||
if filter.Contains(*value) {
|
||||
return true
|
||||
}
|
||||
if filter.Contains("*") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newStructEncoder(t reflect.Type, tagkey string) encoderFunc {
|
||||
se := structEncoder{fields: cachedTypeFields(t, tagkey)}
|
||||
return se.encode
|
||||
}
|
||||
|
||||
@@ -732,7 +792,11 @@ type mapEncoder struct {
|
||||
|
||||
func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
if v.IsNil() {
|
||||
e.WriteString("null")
|
||||
if opts.nilSafeMaps {
|
||||
e.WriteString("{}")
|
||||
} else {
|
||||
e.WriteString("null")
|
||||
}
|
||||
return
|
||||
}
|
||||
if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter {
|
||||
@@ -775,7 +839,7 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
e.ptrLevel--
|
||||
}
|
||||
|
||||
func newMapEncoder(t reflect.Type) encoderFunc {
|
||||
func newMapEncoder(t reflect.Type, tagkey string) encoderFunc {
|
||||
switch t.Key().Kind() {
|
||||
case reflect.String,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
@@ -785,13 +849,17 @@ func newMapEncoder(t reflect.Type) encoderFunc {
|
||||
return unsupportedTypeEncoder
|
||||
}
|
||||
}
|
||||
me := mapEncoder{typeEncoder(t.Elem())}
|
||||
me := mapEncoder{typeEncoder(t.Elem(), tagkey)}
|
||||
return me.encode
|
||||
}
|
||||
|
||||
func encodeByteSlice(e *encodeState, v reflect.Value, _ encOpts) {
|
||||
func encodeByteSlice(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
if v.IsNil() {
|
||||
e.WriteString("null")
|
||||
if opts.nilSafeSlices {
|
||||
e.WriteString(`""`)
|
||||
} else {
|
||||
e.WriteString("null")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -810,7 +878,11 @@ type sliceEncoder struct {
|
||||
|
||||
func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
if v.IsNil() {
|
||||
e.WriteString("null")
|
||||
if opts.nilSafeSlices {
|
||||
e.WriteString("[]")
|
||||
} else {
|
||||
e.WriteString("null")
|
||||
}
|
||||
return
|
||||
}
|
||||
if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter {
|
||||
@@ -832,7 +904,7 @@ func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
e.ptrLevel--
|
||||
}
|
||||
|
||||
func newSliceEncoder(t reflect.Type) encoderFunc {
|
||||
func newSliceEncoder(t reflect.Type, tagkey string) encoderFunc {
|
||||
// Byte slices get special treatment; arrays don't.
|
||||
if t.Elem().Kind() == reflect.Uint8 {
|
||||
p := reflect.PointerTo(t.Elem())
|
||||
@@ -840,7 +912,7 @@ func newSliceEncoder(t reflect.Type) encoderFunc {
|
||||
return encodeByteSlice
|
||||
}
|
||||
}
|
||||
enc := sliceEncoder{newArrayEncoder(t)}
|
||||
enc := sliceEncoder{newArrayEncoder(t, tagkey)}
|
||||
return enc.encode
|
||||
}
|
||||
|
||||
@@ -860,8 +932,8 @@ func (ae arrayEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
e.WriteByte(']')
|
||||
}
|
||||
|
||||
func newArrayEncoder(t reflect.Type) encoderFunc {
|
||||
enc := arrayEncoder{typeEncoder(t.Elem())}
|
||||
func newArrayEncoder(t reflect.Type, tagkey string) encoderFunc {
|
||||
enc := arrayEncoder{typeEncoder(t.Elem(), tagkey)}
|
||||
return enc.encode
|
||||
}
|
||||
|
||||
@@ -888,8 +960,8 @@ func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
|
||||
e.ptrLevel--
|
||||
}
|
||||
|
||||
func newPtrEncoder(t reflect.Type) encoderFunc {
|
||||
enc := ptrEncoder{typeEncoder(t.Elem())}
|
||||
func newPtrEncoder(t reflect.Type, tagkey string) encoderFunc {
|
||||
enc := ptrEncoder{typeEncoder(t.Elem(), tagkey)}
|
||||
return enc.encode
|
||||
}
|
||||
|
||||
@@ -1044,15 +1116,28 @@ type field struct {
|
||||
nameNonEsc string // `"` + name + `":`
|
||||
nameEscHTML string // `"` + HTMLEscape(name) + `":`
|
||||
|
||||
tag bool
|
||||
index []int
|
||||
typ reflect.Type
|
||||
omitEmpty bool
|
||||
quoted bool
|
||||
tag bool
|
||||
index []int
|
||||
typ reflect.Type
|
||||
omitEmpty bool
|
||||
jsonfilter jsonfilter
|
||||
quoted bool
|
||||
|
||||
encoder encoderFunc
|
||||
}
|
||||
|
||||
// jsonfilter stores the value of the jsonfilter struct tag
|
||||
type jsonfilter []string
|
||||
|
||||
func (j jsonfilter) Contains(t string) bool {
|
||||
for _, tag := range j {
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// typeFields returns a list of fields that JSON should recognize for the given type.
|
||||
// The algorithm is breadth-first search over the set of structs to include - the top struct
|
||||
// and then any reachable anonymous structs.
|
||||
@@ -1066,7 +1151,7 @@ type field struct {
|
||||
// See go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname typeFields
|
||||
func typeFields(t reflect.Type) structFields {
|
||||
func typeFields(t reflect.Type, tagkey string) structFields {
|
||||
// Anonymous fields to explore at the current level and the next.
|
||||
current := []field{}
|
||||
next := []field{{typ: t}}
|
||||
@@ -1111,7 +1196,7 @@ func typeFields(t reflect.Type) structFields {
|
||||
// Ignore unexported non-embedded fields.
|
||||
continue
|
||||
}
|
||||
tag := sf.Tag.Get("json")
|
||||
tag := sf.Tag.Get(tagkey)
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
@@ -1119,6 +1204,13 @@ func typeFields(t reflect.Type) structFields {
|
||||
if !isValidTag(name) {
|
||||
name = ""
|
||||
}
|
||||
|
||||
var jsonfilterVal []string
|
||||
jsonfilterTag := sf.Tag.Get("jsonfilter")
|
||||
if jsonfilterTag != "" {
|
||||
jsonfilterVal = strings.Split(jsonfilterTag, ",")
|
||||
}
|
||||
|
||||
index := make([]int, len(f.index)+1)
|
||||
copy(index, f.index)
|
||||
index[len(f.index)] = i
|
||||
@@ -1149,12 +1241,13 @@ func typeFields(t reflect.Type) structFields {
|
||||
name = sf.Name
|
||||
}
|
||||
field := field{
|
||||
name: name,
|
||||
tag: tagged,
|
||||
index: index,
|
||||
typ: ft,
|
||||
omitEmpty: opts.Contains("omitempty"),
|
||||
quoted: quoted,
|
||||
name: name,
|
||||
tag: tagged,
|
||||
index: index,
|
||||
typ: ft,
|
||||
omitEmpty: opts.Contains("omitempty"),
|
||||
jsonfilter: jsonfilterVal,
|
||||
quoted: quoted,
|
||||
}
|
||||
field.nameBytes = []byte(field.name)
|
||||
|
||||
@@ -1237,7 +1330,7 @@ func typeFields(t reflect.Type) structFields {
|
||||
|
||||
for i := range fields {
|
||||
f := &fields[i]
|
||||
f.encoder = typeEncoder(typeByIndex(t, f.index))
|
||||
f.encoder = typeEncoder(typeByIndex(t, f.index), tagkey)
|
||||
}
|
||||
exactNameIndex := make(map[string]*field, len(fields))
|
||||
foldedNameIndex := make(map[string]*field, len(fields))
|
||||
@@ -1267,14 +1360,14 @@ func dominantField(fields []field) (field, bool) {
|
||||
return fields[0], true
|
||||
}
|
||||
|
||||
var fieldCache sync.Map // map[reflect.Type]structFields
|
||||
var fieldCache sync.Map // map[reflect.Type + tagkey]structFields
|
||||
|
||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||
func cachedTypeFields(t reflect.Type) structFields {
|
||||
if f, ok := fieldCache.Load(t); ok {
|
||||
func cachedTypeFields(t reflect.Type, tagkey string) structFields {
|
||||
if f, ok := fieldCache.Load(TagKeyTypeKey{t, tagkey}); ok {
|
||||
return f.(structFields)
|
||||
}
|
||||
f, _ := fieldCache.LoadOrStore(t, typeFields(t))
|
||||
f, _ := fieldCache.LoadOrStore(TagKeyTypeKey{t, tagkey}, typeFields(t, tagkey))
|
||||
return f.(structFields)
|
||||
}
|
||||
|
||||
@@ -1284,3 +1377,8 @@ func mayAppendQuote(b []byte, quoted bool) []byte {
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type TagKeyTypeKey struct {
|
||||
Type reflect.Type
|
||||
TagKey string
|
||||
}
|
||||
|
Reference in New Issue
Block a user