WIP: add test case for MarshalJSON type

This commit is contained in:
Masaaki Goshima 2021-03-05 13:23:47 +09:00
parent 174fd57eae
commit d3b031cef2
5 changed files with 3306 additions and 1037 deletions

View File

@ -281,7 +281,7 @@ func (t opType) fieldToStringTagField() opType {
"array", "map", "mapLoad", "slice", "struct", "MarshalJSON", "MarshalText", "recursive",
"intString", "uintString",
"intPtr", "uintPtr", "float32Ptr", "float64Ptr", "boolPtr", "stringPtr", "bytesPtr",
"arrayPtr", "slicePtr", "mapPtr",
"arrayPtr", "slicePtr", "mapPtr", "marshalJSONPtr",
"intNPtr", "uintNPtr", "float32NPtr", "float64NPtr", "boolNPtr", "stringNPtr", "bytesNPtr",
}
primitiveTypesUpper := []string{}

2138
cover_marshal_json_test.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -57,9 +57,12 @@ func encodeCompileHead(ctx *encodeCompileContext) (*opcode, error) {
typ := ctx.typ
switch {
case typ.Implements(marshalJSONType):
return encodeCompileMarshalJSON(ctx)
case rtype_ptrTo(typ).Implements(marshalJSONType):
return encodeCompileMarshalJSONPtr(ctx)
if typ.Kind() != reflect.Ptr || !typ.Elem().Implements(marshalJSONType) {
fmt.Println("typ = ", typ)
return encodeCompileMarshalJSON(ctx)
}
// case rtype_ptrTo(typ).Implements(marshalJSONType):
// return encodeCompileMarshalJSONPtr(ctx)
case typ.Implements(marshalTextType):
return encodeCompileMarshalText(ctx)
case rtype_ptrTo(typ).Implements(marshalTextType):
@ -78,24 +81,18 @@ func encodeCompileHead(ctx *encodeCompileContext) (*opcode, error) {
if err != nil {
return nil, err
}
if !code.indirect && isPtr {
code.indirect = true
}
encodeOptimizeStructEnd(code)
encodeLinkRecursiveCode(code)
return code, nil
} else if isPtr && typ.Implements(marshalTextType) {
typ = orgType
} else if isPtr && typ.Implements(marshalJSONType) {
typ = orgType
// } else if isPtr && typ.Implements(marshalJSONType) {
// typ = orgType
}
code, err := encodeCompile(ctx.withType(typ), isPtr)
if err != nil {
return nil, err
}
if !code.indirect && isPtr {
code.indirect = true
}
encodeOptimizeStructEnd(code)
encodeLinkRecursiveCode(code)
return code, nil
@ -171,7 +168,8 @@ func encodeOptimizeStructEnd(c *opcode) {
if strings.Contains(prevOp, "Head") ||
strings.Contains(prevOp, "Slice") ||
strings.Contains(prevOp, "Array") ||
strings.Contains(prevOp, "Map") {
strings.Contains(prevOp, "Map") ||
strings.Contains(prevOp, "MarshalJSON") {
// not exists field
code = code.next
break
@ -193,9 +191,10 @@ func encodeOptimizeStructEnd(c *opcode) {
func encodeImplementsMarshaler(typ *rtype) bool {
switch {
case typ.Implements(marshalJSONType):
fmt.Println("MarshalJSON", typ)
return true
case rtype_ptrTo(typ).Implements(marshalJSONType):
return true
// case rtype_ptrTo(typ).Implements(marshalJSONType):
// return true
case typ.Implements(marshalTextType):
return true
case rtype_ptrTo(typ).Implements(marshalTextType):
@ -207,10 +206,13 @@ func encodeImplementsMarshaler(typ *rtype) bool {
func encodeCompile(ctx *encodeCompileContext, isPtr bool) (*opcode, error) {
typ := ctx.typ
switch {
case typ.Implements(marshalJSONType):
return encodeCompileMarshalJSON(ctx)
case rtype_ptrTo(typ).Implements(marshalJSONType):
case isPtr && typ.Kind() == reflect.Ptr && typ.Implements(marshalJSONType) && !typ.Elem().Implements(marshalJSONType):
// *struct{ field *implementedMarshalJSONType }
return encodeCompileMarshalJSONPtr(ctx)
case typ.Implements(marshalJSONType) && (typ.Kind() != reflect.Ptr || !typ.Elem().Implements(marshalJSONType)):
return encodeCompileMarshalJSON(ctx)
// case rtype_ptrTo(typ).Implements(marshalJSONType):
// return encodeCompileMarshalJSONPtr(ctx)
case typ.Implements(marshalTextType):
return encodeCompileMarshalText(ctx)
case rtype_ptrTo(typ).Implements(marshalTextType):
@ -271,6 +273,7 @@ func encodeCompileKey(ctx *encodeCompileContext) (*opcode, error) {
typ := ctx.typ
switch {
case rtype_ptrTo(typ).Implements(marshalJSONType):
fmt.Println("key MarshalJSON", typ)
return encodeCompileMarshalJSONPtr(ctx)
case rtype_ptrTo(typ).Implements(marshalTextType):
return encodeCompileMarshalTextPtr(ctx)
@ -330,13 +333,15 @@ func encodeCompilePtr(ctx *encodeCompileContext) (*opcode, error) {
}
func encodeCompileMarshalJSON(ctx *encodeCompileContext) (*opcode, error) {
fmt.Println("encodeCompileMarshalJSON", ctx.typ)
code := newOpCode(ctx, opMarshalJSON)
ctx.incIndex()
return code, nil
}
func encodeCompileMarshalJSONPtr(ctx *encodeCompileContext) (*opcode, error) {
code := newOpCode(ctx.withType(rtype_ptrTo(ctx.typ)), opMarshalJSON)
fmt.Println("encodeCompileMarshalJSONPtr")
code := newOpCode(ctx.withType(rtype_ptrTo(ctx.typ)), opMarshalJSONPtr)
ctx.incIndex()
return code, nil
}
@ -761,6 +766,8 @@ func encodeTypeToHeaderType(ctx *encodeCompileContext, code *opcode) opType {
return opStructFieldHeadArrayPtr
case opMapHead:
return opStructFieldHeadMapPtr
case opMarshalJSON:
return opStructFieldHeadMarshalJSONPtr
}
}
case opInt:
@ -787,6 +794,8 @@ func encodeTypeToHeaderType(ctx *encodeCompileContext, code *opcode) opType {
return opStructFieldHeadStruct
case opMarshalJSON:
return opStructFieldHeadMarshalJSON
case opMarshalJSONPtr:
return opStructFieldHeadMarshalJSONPtr
case opMarshalText:
return opStructFieldHeadMarshalText
}
@ -850,6 +859,8 @@ func encodeTypeToFieldType(ctx *encodeCompileContext, code *opcode) opType {
return opStructFieldArrayPtr
case opMapHead:
return opStructFieldMapPtr
case opMarshalJSON:
return opStructFieldMarshalJSONPtr
}
}
case opInt:
@ -876,6 +887,8 @@ func encodeTypeToFieldType(ctx *encodeCompileContext, code *opcode) opType {
return opStructFieldStruct
case opMarshalJSON:
return opStructFieldMarshalJSON
case opMarshalJSONPtr:
return opStructFieldMarshalJSONPtr
case opMarshalText:
return opStructFieldMarshalText
}
@ -956,6 +969,12 @@ func encodeStructHeader(ctx *encodeCompileContext, fieldCode *opcode, valueCode
opStructFieldHeadStringTagMapPtr:
*valueCode = *valueCode.next
return valueCode.beforeLastCode()
case opStructFieldHeadMarshalJSONPtr,
opStructFieldHeadOmitEmptyMarshalJSONPtr,
opStructFieldHeadStringTagMarshalJSONPtr:
ctx.decOpcodeIndex()
fieldCode.typ = fieldCode.typ.Elem()
return (*opcode)(unsafe.Pointer(fieldCode))
}
ctx.decOpcodeIndex()
return (*opcode)(unsafe.Pointer(fieldCode))
@ -1230,21 +1249,38 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error)
for i, tag := range tags {
field := tag.field
fieldType := type2rtype(field.Type)
if isPtr && i == 0 {
// head field of pointer structure at top level
// if field type is pointer and implements MarshalJSON or MarshalText,
// it need to operation of dereference of pointer.
if field.Type.Kind() == reflect.Ptr &&
(field.Type.Implements(marshalJSONType) || field.Type.Implements(marshalTextType)) {
fieldType = rtype_ptrTo(fieldType)
/*
if isPtr && i == 0 {
// head field of pointer structure at top level
// if field type is pointer and implements MarshalJSON or MarshalText,
// it need to operation of dereference of pointer.
if field.Type.Kind() == reflect.Ptr &&
(field.Type.Implements(marshalJSONType) || field.Type.Implements(marshalTextType)) {
fieldType = rtype_ptrTo(fieldType)
}
}
}
*/
fieldOpcodeIndex := ctx.opcodeIndex
fieldPtrIndex := ctx.ptrIndex
ctx.incIndex()
valueCode, err := encodeCompile(ctx.withType(fieldType), false)
if err != nil {
return nil, err
var valueCode *opcode
if i == 0 && fieldNum == 1 && isPtr && rtype_ptrTo(fieldType).Implements(marshalJSONType) && !fieldType.Implements(marshalJSONType) {
// *struct{ field implementedMarshalJSONType } => struct { field *implementedMarshalJSONType }
// move pointer position from head to first field
ctx.typ = rtype_ptrTo(fieldType)
code, err := encodeCompileMarshalJSON(ctx)
if err != nil {
return nil, err
}
valueCode = code
indirect = false
isPtr = false
} else {
code, err := encodeCompile(ctx.withType(fieldType), i == 0 && isPtr)
if err != nil {
return nil, err
}
valueCode = code
}
if field.Anonymous {
@ -1337,5 +1373,9 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error)
delete(ctx.structTypeToCompiledCode, typeptr)
if !code.indirect && isPtr {
code.indirect = true
}
return ret, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -74,6 +74,7 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco
ptrOffset := uintptr(0)
ctxptr := ctx.ptr()
code := codeSet.code
fmt.Println(code.dump())
for {
switch code.op {
@ -543,7 +544,7 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco
}
b = append(b, '{')
p += code.offset
if p == 0 || *(*uintptr)(*(*unsafe.Pointer)(unsafe.Pointer(&p))) == 0 {
if p == 0 {
code = code.nextField
} else {
b = append(b, code.key...)
@ -3213,7 +3214,7 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco
code = code.next
store(ctxptr, code.idx, p)
}
case opStructFieldPtrHeadMarshalJSON:
case opStructFieldPtrHeadMarshalJSON, opStructFieldPtrHeadStringTagMarshalJSON:
p := load(ctxptr, code.idx)
if p == 0 {
b = encodeNull(b)
@ -3223,7 +3224,7 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco
}
store(ctxptr, code.idx, ptrToPtr(p))
fallthrough
case opStructFieldHeadMarshalJSON:
case opStructFieldHeadMarshalJSON, opStructFieldHeadStringTagMarshalJSON:
p := load(ctxptr, code.idx)
if p == 0 && code.indirect {
b = encodeNull(b)
@ -3233,11 +3234,15 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco
}
b = append(b, '{')
b = append(b, code.key...)
bb, err := encodeMarshalJSON(b, ptrToInterface(code, p+code.offset))
if err != nil {
return nil, err
if p == 0 || code.indirect && code.typ.Kind() == reflect.Ptr && ptrToPtr(p) == 0 {
b = encodeNull(b)
} else {
bb, err := encodeMarshalJSON(b, ptrToInterface(code, p+code.offset))
if err != nil {
return nil, err
}
b = bb
}
b = bb
b = encodeComma(b)
code = code.next
case opStructFieldPtrHeadOmitEmptyMarshalJSON:
@ -3251,46 +3256,103 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco
store(ctxptr, code.idx, ptrToPtr(p))
fallthrough
case opStructFieldHeadOmitEmptyMarshalJSON:
ptr := load(ctxptr, code.idx)
if ptr == 0 && code.indirect {
p := load(ctxptr, code.idx)
if p == 0 && code.indirect {
b = encodeNull(b)
b = encodeComma(b)
code = code.end.next
break
}
b = append(b, '{')
p := ptrToUnsafePtr(ptr + code.offset)
isPtr := code.typ.Kind() == reflect.Ptr
if p == nil || (!isPtr && *(*unsafe.Pointer)(p) == nil) {
if p == 0 || code.indirect && code.typ.Kind() == reflect.Ptr && ptrToPtr(p) == 0 {
code = code.nextField
} else {
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{typ: code.typ, ptr: p}))
bb, err := v.(Marshaler).MarshalJSON()
b = append(b, code.key...)
bb, err := encodeMarshalJSON(b, ptrToInterface(code, p+code.offset))
if err != nil {
return nil, &MarshalerError{
Type: rtype2type(code.typ),
Err: err,
}
return nil, err
}
if len(bb) == 0 {
if isPtr {
return nil, errUnexpectedEndOfJSON(
fmt.Sprintf("error calling MarshalJSON for type %s", code.typ),
0,
)
b = bb
b = encodeComma(b)
code = code.next
/*
p := ptrToUnsafePtr(p + code.offset)
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{typ: code.typ, ptr: p}))
bb, err := v.(Marshaler).MarshalJSON()
if err != nil {
return nil, &MarshalerError{
Type: rtype2type(code.typ),
Err: err,
}
}
code = code.nextField
} else {
b = append(b, code.key...)
buf := bytes.NewBuffer(b)
//TODO: we should validate buffer with `compact`
if err := compact(buf, bb, false); err != nil {
return nil, err
if len(bb) == 0 {
if isPtr {
return nil, errUnexpectedEndOfJSON(
fmt.Sprintf("error calling MarshalJSON for type %s", code.typ),
0,
)
}
code = code.nextField
} else {
b = append(b, code.key...)
buf := bytes.NewBuffer(b)
//TODO: we should validate buffer with `compact`
if err := compact(buf, bb, false); err != nil {
return nil, err
}
b = buf.Bytes()
b = encodeComma(b)
code = code.next
}
b = buf.Bytes()
b = encodeComma(b)
code = code.next
*/
}
case opStructFieldHeadMarshalJSONPtr, opStructFieldHeadStringTagMarshalJSONPtr:
p := load(ctxptr, code.idx)
if p == 0 && code.indirect {
b = encodeNull(b)
b = encodeComma(b)
code = code.end.next
break
}
b = append(b, '{')
b = append(b, code.key...)
if code.indirect {
p = ptrToPtr(p + code.offset)
}
if p == 0 {
b = encodeNull(b)
} else {
bb, err := encodeMarshalJSON(b, ptrToInterface(code, p))
if err != nil {
return nil, err
}
b = bb
}
b = encodeComma(b)
code = code.next
case opStructFieldHeadOmitEmptyMarshalJSONPtr:
p := load(ctxptr, code.idx)
if p == 0 && code.indirect {
b = encodeNull(b)
b = encodeComma(b)
code = code.end.next
break
}
if code.indirect {
p = ptrToPtr(p + code.offset)
}
b = append(b, '{')
if p == 0 {
code = code.nextField
} else {
b = append(b, code.key...)
bb, err := encodeMarshalJSON(b, ptrToInterface(code, p+code.offset))
if err != nil {
return nil, err
}
b = bb
b = encodeComma(b)
code = code.next
}
case opStructFieldPtrAnonymousHeadMarshalJSON:
p := load(ctxptr, code.idx)
@ -3478,53 +3540,6 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco
code = code.next
}
}
case opStructFieldPtrHeadStringTagMarshalJSON:
ptr := load(ctxptr, code.idx)
if ptr != 0 {
store(ctxptr, code.idx, ptrToPtr(ptr))
}
fallthrough
case opStructFieldHeadStringTagMarshalJSON:
ptr := load(ctxptr, code.idx)
if ptr == 0 {
b = encodeNull(b)
b = encodeComma(b)
code = code.end.next
} else {
b = append(b, '{')
ptr += code.offset
p := ptrToUnsafePtr(ptr)
isPtr := code.typ.Kind() == reflect.Ptr
v := *(*interface{})(unsafe.Pointer(&interfaceHeader{typ: code.typ, ptr: p}))
bb, err := v.(Marshaler).MarshalJSON()
if err != nil {
return nil, &MarshalerError{
Type: rtype2type(code.typ),
Err: err,
}
}
if len(bb) == 0 {
if isPtr {
return nil, errUnexpectedEndOfJSON(
fmt.Sprintf("error calling MarshalJSON for type %s", code.typ),
0,
)
}
b = append(b, code.key...)
b = append(b, '"', '"')
b = encodeComma(b)
code = code.nextField
} else {
var buf bytes.Buffer
if err := compact(&buf, bb, false); err != nil {
return nil, err
}
b = append(b, code.key...)
b = encodeNoEscapedString(b, buf.String())
b = encodeComma(b)
code = code.next
}
}
case opStructFieldPtrAnonymousHeadStringTagMarshalJSON:
ptr := load(ctxptr, code.idx)
if ptr != 0 {
@ -3631,7 +3646,7 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco
case opStructFieldOmitEmpty:
ptr := load(ctxptr, code.headIdx)
p := ptr + code.offset
if p == 0 || **(**uintptr)(unsafe.Pointer(&p)) == 0 {
if p == 0 {
code = code.nextField
} else {
b = append(b, code.key...)
@ -4074,43 +4089,63 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco
}
b = encodeComma(b)
code = code.next
case opStructFieldMarshalJSON:
ptr := load(ctxptr, code.headIdx)
case opStructFieldMarshalJSON, opStructFieldStringTagMarshalJSON:
p := load(ctxptr, code.headIdx)
b = append(b, code.key...)
p := ptr + code.offset
v := ptrToInterface(code, p)
bb, err := v.(Marshaler).MarshalJSON()
if err != nil {
return nil, errMarshaler(code, err)
p += code.offset
if code.typ.Kind() == reflect.Ptr && p != 0 && ptrToPtr(p) == 0 {
b = encodeNull(b)
} else {
v := ptrToInterface(code, p)
bb, err := encodeMarshalJSON(b, v)
if err != nil {
return nil, err
}
b = bb
}
buf := bytes.NewBuffer(b)
//TODO: we should validate buffer with `compact`
if err := compact(buf, bb, false); err != nil {
return nil, err
}
b = buf.Bytes()
b = encodeComma(b)
code = code.next
case opStructFieldStringTagMarshalJSON:
ptr := load(ctxptr, code.headIdx)
p := ptr + code.offset
v := ptrToInterface(code, p)
bb, err := v.(Marshaler).MarshalJSON()
if err != nil {
return nil, errMarshaler(code, err)
}
var buf bytes.Buffer
if err := compact(&buf, bb, false); err != nil {
return nil, err
}
b = append(b, code.key...)
b = encodeNoEscapedString(b, buf.String())
b = encodeComma(b)
code = code.next
case opStructFieldOmitEmptyMarshalJSON:
ptr := load(ctxptr, code.headIdx)
p := ptr + code.offset
if code.typ.Kind() == reflect.Ptr && code.typ.Elem().Implements(marshalJSONType) {
if code.typ.Kind() == reflect.Ptr && p != 0 && ptrToPtr(p) == 0 {
code = code.nextField
break
}
v := ptrToInterface(code, p)
if v == nil {
code = code.nextField
break
}
b = append(b, code.key...)
bb, err := encodeMarshalJSON(b, v)
if err != nil {
return nil, err
}
b = encodeComma(bb)
code = code.next
case opStructFieldMarshalJSONPtr, opStructFieldStringTagMarshalJSONPtr:
p := load(ctxptr, code.headIdx)
b = append(b, code.key...)
p += code.offset
if p != 0 {
p = ptrToPtr(p)
}
if p == 0 {
b = encodeNull(b)
} else {
bb, err := encodeMarshalJSON(b, ptrToInterface(code, p))
if err != nil {
return nil, err
}
b = bb
}
b = encodeComma(b)
code = code.next
case opStructFieldOmitEmptyMarshalJSONPtr:
p := load(ctxptr, code.headIdx)
p += code.offset
if p != 0 {
p = ptrToPtr(p)
}
v := ptrToInterface(code, p)