Apply goext specific patches to gojson
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Has been cancelled

This commit is contained in:
2025-01-10 14:01:13 +01:00
parent c8e9c34706
commit ff821390f7
10 changed files with 269 additions and 638 deletions

View File

@@ -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
}