diff --git a/decode_test.go b/decode_test.go index f52069b..ea88d76 100644 --- a/decode_test.go +++ b/decode_test.go @@ -3732,3 +3732,47 @@ func TestDecodeBinaryTypeWithEscapedChar(t *testing.T) { } }) } + +func TestIssue282(t *testing.T) { + var J = []byte(`{ + "a": {}, + "b": {}, + "c": {}, + "d": {}, + "e": {}, + "f": {}, + "g": {}, + "h": { + "m": "1" + }, + "i": {} +}`) + + type T4 struct { + F0 string + F1 string + F2 string + F3 string + F4 string + F5 string + F6 int + } + type T3 struct { + F0 string + F1 T4 + } + type T2 struct { + F0 string `json:"m"` + F1 T3 + } + type T0 map[string]T2 + + // T2 size is 136 bytes. This is indirect type. + var v T0 + if err := json.Unmarshal(J, &v); err != nil { + t.Fatal(err) + } + if v["h"].F0 != "1" { + t.Fatalf("failed to assign map value") + } +} diff --git a/internal/decoder/map.go b/internal/decoder/map.go index dd480e1..bb18ef9 100644 --- a/internal/decoder/map.go +++ b/internal/decoder/map.go @@ -9,29 +9,42 @@ import ( ) type mapDecoder struct { - mapType *runtime.Type - keyType *runtime.Type - valueType *runtime.Type - stringKeyType bool - keyDecoder Decoder - valueDecoder Decoder - structName string - fieldName string + mapType *runtime.Type + keyType *runtime.Type + valueType *runtime.Type + canUseAssignFaststrType bool + keyDecoder Decoder + valueDecoder Decoder + structName string + fieldName string } func newMapDecoder(mapType *runtime.Type, keyType *runtime.Type, keyDec Decoder, valueType *runtime.Type, valueDec Decoder, structName, fieldName string) *mapDecoder { return &mapDecoder{ - mapType: mapType, - keyDecoder: keyDec, - keyType: keyType, - stringKeyType: keyType.Kind() == reflect.String, - valueType: valueType, - valueDecoder: valueDec, - structName: structName, - fieldName: fieldName, + mapType: mapType, + keyDecoder: keyDec, + keyType: keyType, + canUseAssignFaststrType: canUseAssignFaststrType(keyType, valueType), + valueType: valueType, + valueDecoder: valueDec, + structName: structName, + fieldName: fieldName, } } +const ( + mapMaxElemSize = 128 +) + +// See detail: https://github.com/goccy/go-json/pull/283 +func canUseAssignFaststrType(key *runtime.Type, value *runtime.Type) bool { + indirectElem := value.Size() > mapMaxElemSize + if indirectElem { + return false + } + return key.Kind() == reflect.String +} + //go:linkname makemap reflect.makemap func makemap(*runtime.Type, int) unsafe.Pointer @@ -45,8 +58,8 @@ func mapassign_faststr(t *runtime.Type, m unsafe.Pointer, s string) unsafe.Point func mapassign(t *runtime.Type, m unsafe.Pointer, k, v unsafe.Pointer) func (d *mapDecoder) mapassign(t *runtime.Type, m, k, v unsafe.Pointer) { - if d.stringKeyType { - mapV := mapassign_faststr(d.mapType, m, *(*string)(k)) + if d.canUseAssignFaststrType { + mapV := mapassign_faststr(t, m, *(*string)(k)) typedmemmove(d.valueType, mapV, v) } else { mapassign(t, m, k, v)