diff --git a/README.md b/README.md index 6a1ae34..39f6fb5 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Roadmap: - [x] Primitive types - [x] Struct types - [x] Type `struct{}` - - [ ] Recursive struct types + - [x] Recursive struct types - [x] Slices - [x] Arrays - [x] Type `any` diff --git a/jscandec.go b/jscandec.go index 8661c1d..9a3e7ec 100644 --- a/jscandec.go +++ b/jscandec.go @@ -1189,7 +1189,6 @@ func (d *Decoder[S, T]) Decode(s S, t *T, options *DecodeOptions) (err ErrorDeco si := uint32(0) d.stackExp[0].Dest = unsafe.Pointer(t) - // fmt.Printf("VROOT %p\n", d.stackExp[0].Dest) errTok := d.tokenizer.Tokenize(s, func(tokens []jscan.Token[S]) (exit bool) { // ti stands for the token index and points at the current token @@ -1208,8 +1207,7 @@ func (d *Decoder[S, T]) Decode(s S, t *T, options *DecodeOptions) (err ErrorDeco *(*unsafe.Pointer)(p) = dp } continue - case ExpectTypePtrRecur: - panic("TODO") + case ExpectTypeJSONUnmarshaler: goto ON_JSON_UNMARSHALER } @@ -1875,6 +1873,8 @@ func (d *Decoder[S, T]) Decode(s S, t *T, options *DecodeOptions) (err ErrorDeco // Skip case ExpectTypeStruct, ExpectTypeStructRecur: // Skip + case ExpectTypePtr, ExpectTypePtrRecur: + *(*unsafe.Pointer)(p) = nil case ExpectTypeBool: *(*bool)(p) = zeroBool case ExpectTypeStr: @@ -2841,6 +2841,52 @@ func (d *Decoder[S, T]) Decode(s S, t *T, options *DecodeOptions) (err ErrorDeco ti = len(tokens) - len(tail) goto ON_VAL_END + case ExpectTypePtrRecur: + p := unsafe.Pointer( + uintptr(d.stackExp[si].Dest) + d.stackExp[si].Offset, + ) + + siPointer := si + si = uint32(d.stackExp[si].CapOrRecurFrame) + // Push recursion stack + // fmt.Printf("%d\tPUSH STACK %p OFFSET %d SI %d\n", + // ti, + // d.stackExp[si].Dest, + // d.stackExp[si].Offset, + // si) + d.stackExp[si].RecursionStack = append( + d.stackExp[si].RecursionStack, recursionStackFrame{ + Dest: d.stackExp[si].Dest, + Offset: d.stackExp[si].Offset, + ContainerFrame: siPointer, + }, + ) + // fmt.Printf("%d\tRECSTACK: %v\n", ti, d.stackExp[si].RecursionStack) + + var dp unsafe.Pointer + if *(*unsafe.Pointer)(p) != nil { + dp = *(*unsafe.Pointer)(p) + // fmt.Printf("%d\tJUST SET %p\n", ti, dp) + } else { + dp = allocate(d.stackExp[si].Size) + // fmt.Printf( + // "%d\tALLOCATED NEW INSTANCE %p SIZE %d SET SI %d WRITE AT %p OFFSET %d\n", + // ti, dp, d.stackExp[si].Size, si, d.stackExp[si].Dest, d.stackExp[si].Offset) + *(*unsafe.Pointer)(p) = dp + } + d.stackExp[si].Dest = dp + + if tokens[ti].Elements == 0 { + ti += 2 + goto ON_RECUR_OBJ_END + } + // Point all fields to the struct, the offsets are already + // set statically during decoder init time. + for i := range d.stackExp[si].Fields { + d.stackExp[d.stackExp[si].Fields[i].FrameIndex].Dest = dp + } + ti++ + case ExpectTypeMapRecur: // Push recursion stack recursiveFrame := d.stackExp[si].CapOrRecurFrame @@ -2863,7 +2909,7 @@ func (d *Decoder[S, T]) Decode(s S, t *T, options *DecodeOptions) (err ErrorDeco *(*unsafe.Pointer)(p) = makemap( d.stackExp[si].MapType, tokens[ti].Elements, ) - // fmt.Printf("INIT MAP %p TO %p OFFSET %d\n", + // fmt.Printf("🌈 INIT MAP %p TO %p OFFSET %d\n", // *(*unsafe.Pointer)(p), d.stackExp[si].Dest, d.stackExp[si].Offset) } if tokens[ti].Elements == 0 { @@ -3015,7 +3061,7 @@ func (d *Decoder[S, T]) Decode(s S, t *T, options *DecodeOptions) (err ErrorDeco key := s[tokens[ti].Index+1 : tokens[ti].End-1] - // fmt.Printf("KEY %q SI %d ON %p\n", key, si, d.stackExp[si].Dest) + // fmt.Printf("\nKEY %q SI %d ON %p\n", key, si, d.stackExp[si].Dest) typMap := d.stackExp[si].MapType typVal := d.stackExp[si].MapValueType @@ -3284,7 +3330,7 @@ func (d *Decoder[S, T]) Decode(s S, t *T, options *DecodeOptions) (err ErrorDeco ti++ case jscan.TokenTypeObjectEnd: - // fmt.Printf("OBJ END TI %d SI %d\n", ti, si) + // fmt.Printf("%d\tOBJ END SI %d\n", ti, si) ti++ switch d.stackExp[si].Type { case ExpectTypeStruct: @@ -3296,7 +3342,7 @@ func (d *Decoder[S, T]) Decode(s S, t *T, options *DecodeOptions) (err ErrorDeco recurStack := d.stackExp[recursiveFrame].RecursionStack if len(recurStack) < 1 { // In map of the root recursive struct - // si = uint32(d.stackExp[si].CapOrRecurFrame) + si = uint32(d.stackExp[si].CapOrRecurFrame) // fmt.Printf("%d\tIN ROOT MAP\n", ti) goto ON_VAL_END } @@ -3357,15 +3403,44 @@ func (d *Decoder[S, T]) Decode(s S, t *T, options *DecodeOptions) (err ErrorDeco ON_RECUR_OBJ_END: if l := len(d.stackExp[si].RecursionStack); l > 0 { - siCon := d.stackExp[si].RecursionStack[l-1].ContainerFrame - // fmt.Printf("%d\tRECOBJ END\tSI %d CONTAINER %d\n", - // ti, si, siCon) - switch d.stackExp[siCon].Type { + top := d.stackExp[si].RecursionStack[l-1] + // fmt.Printf("%d\tRECOBJ END\tSI %d TOPDEST %p CONTAINER %d\n", + // ti, si, top.Dest, top.ContainerFrame) + switch d.stackExp[top.ContainerFrame].Type { case ExpectTypeSliceRecur: d.stackExp[si].Offset += d.stackExp[si].Size + case ExpectTypePtrRecur: + recurStack := d.stackExp[si].RecursionStack + if len(recurStack) < 1 { + // In map of the root recursive struct + // fmt.Printf("%d\tIN ROOT STRUCT\n", ti) + goto ON_VAL_END + } + + // Reset to parent context + // dumpStack("BEFORE RESET") + topIndex := len(recurStack) - 1 + d.stackExp[si].Dest = top.Dest + d.stackExp[si].Offset = top.Offset + fields := d.stackExp[si].Fields + for i := range fields { + d.stackExp[fields[i].FrameIndex].Dest = top.Dest + } + + // Pop recursion stack + recurStack[topIndex].Dest = nil + d.stackExp[si].RecursionStack = recurStack[:topIndex] + + si = uint32(d.stackExp[top.ContainerFrame].ParentFrameIndex) + // fmt.Println("EXITED POINTER-RECURSIVE STRUCT", si) + // dumpStack("AFTER RESET") + case ExpectTypeMapRecur: - si = siCon - // fmt.Println("BACK TO RECUR MAP", si) + si = top.ContainerFrame + // Set the top stack destination so the next key gets assigned to it. + // Otherwise we'll be in the destination of the previously assigned + // key, which will point to an empty map. + d.stackExp[si].Dest = top.Dest } continue } diff --git a/jscandec_internal_test.go b/jscandec_internal_test.go index d92c82d..a6beff8 100644 --- a/jscandec_internal_test.go +++ b/jscandec_internal_test.go @@ -79,9 +79,18 @@ func TestAppendTypeToStack(t *testing.T) { type SStringUint64 struct { Uint64 uint64 `json:",string"` } + type SRecurSlice struct { + ID string + Recursion []SRecurSlice + } type SRecurMap struct { + ID string Recursion map[string]SRecurMap } + type SRecurPtr struct { + ID string + Recursion *SRecurPtr + } tpS3 := reflect.TypeOf(S3{}) tpS4 := reflect.TypeOf(S4{}) @@ -1345,18 +1354,64 @@ func TestAppendTypeToStack(t *testing.T) { }, }, { - Input: SRecurMap{}, + Input: []SRecurSlice{}, ExpectStack: []stackFrame[string]{ + { + Type: ExpectTypeSlice, + Size: reflect.TypeOf([]SRecurSlice{}).Size(), + ParentFrameIndex: noParentFrame, + }, { Type: ExpectTypeStructRecur, - RType: reflect.TypeOf(SRecurMap{}), + RType: reflect.TypeOf(SRecurSlice{}), Fields: []fieldStackFrame{ - {FrameIndex: 1, Name: "Recursion"}, + {FrameIndex: 2, Name: "ID"}, + {FrameIndex: 3, Name: "Recursion"}, }, RecursionStack: make([]recursionStackFrame, 0, 64), - Size: reflect.TypeOf(SStringUint64{}).Size(), + Size: reflect.TypeOf(SRecurSlice{}).Size(), + ParentFrameIndex: 0, + }, + { + Type: ExpectTypeStr, + Size: reflect.TypeOf(string("")).Size(), + Offset: reflect.TypeOf(SRecurSlice{}).Field(0).Offset, + ParentFrameIndex: 1, + }, + { + Type: ExpectTypeSliceRecur, + Size: reflect.TypeOf([]SRecurSlice{}).Size(), + Offset: reflect.TypeOf(SRecurSlice{}).Field(1).Offset, + CapOrRecurFrame: 1, + ParentFrameIndex: 1, + }, + }, + }, + { + Input: []SRecurMap{}, + ExpectStack: []stackFrame[string]{ + { + Type: ExpectTypeSlice, + Size: reflect.TypeOf([]SRecurMap{}).Size(), ParentFrameIndex: noParentFrame, }, + { + Type: ExpectTypeStructRecur, + RType: reflect.TypeOf(SRecurMap{}), + Fields: []fieldStackFrame{ + {FrameIndex: 2, Name: "ID"}, + {FrameIndex: 3, Name: "Recursion"}, + }, + RecursionStack: make([]recursionStackFrame, 0, 64), + Size: reflect.TypeOf(SRecurMap{}).Size(), + ParentFrameIndex: 0, + }, + { + Type: ExpectTypeStr, + Size: reflect.TypeOf(string("")).Size(), + Offset: reflect.TypeOf(SRecurMap{}).Field(0).Offset, + ParentFrameIndex: 1, + }, { Type: ExpectTypeMapRecur, MapType: getTyp(reflect.TypeOf( @@ -1369,12 +1424,47 @@ func TestAppendTypeToStack(t *testing.T) { Size: reflect.TypeOf( map[string]SRecurMap{}, ).Size(), - CapOrRecurFrame: 0, + Offset: reflect.TypeOf(SRecurMap{}).Field(1).Offset, + CapOrRecurFrame: 1, + ParentFrameIndex: 1, + }, + { + Type: ExpectTypeStr, + Size: reflect.TypeOf(string("")).Size(), + ParentFrameIndex: 3, + }, + }, + }, + { + Input: []SRecurPtr{}, + ExpectStack: []stackFrame[string]{ + { + Type: ExpectTypeSlice, + Size: reflect.TypeOf([]SRecurPtr{}).Size(), + ParentFrameIndex: noParentFrame, + }, + { + Type: ExpectTypeStructRecur, + RType: reflect.TypeOf(SRecurPtr{}), + Fields: []fieldStackFrame{ + {FrameIndex: 2, Name: "ID"}, + {FrameIndex: 3, Name: "Recursion"}, + }, + RecursionStack: make([]recursionStackFrame, 0, 64), + Size: reflect.TypeOf(SRecurPtr{}).Size(), ParentFrameIndex: 0, }, { Type: ExpectTypeStr, Size: reflect.TypeOf(string("")).Size(), + Offset: reflect.TypeOf(SRecurPtr{}).Field(0).Offset, + ParentFrameIndex: 1, + }, + { + Type: ExpectTypePtrRecur, + Size: reflect.TypeOf((*SRecurPtr)(nil)).Size(), + Offset: reflect.TypeOf(SRecurPtr{}).Field(1).Offset, + CapOrRecurFrame: 1, ParentFrameIndex: 1, }, }, diff --git a/jscandec_test.go b/jscandec_test.go index 7fd639b..c73f3b6 100644 --- a/jscandec_test.go +++ b/jscandec_test.go @@ -1564,6 +1564,130 @@ func TestDecodeStruct(t *testing.T) { jscandec.ErrorDecode{Err: jscandec.ErrUnexpectedValue, Index: 0}) } +func TestDecodeStructRecursivePtr(t *testing.T) { + type S struct { + ID string + Name string + Recurse *S + } + s := newTestSetup[S](t, *jscandec.DefaultOptions) + s.TestOK(t, "null", + `null`, S{}) + s.TestOK(t, "empty", + `{}`, S{}) + s.TestOK(t, "root", + `{"id":"root"}`, S{ID: "root"}) + s.TestOK(t, "2_level", + `{"id":"root","recurse":{"id":"level2", "name": "Level 2"}}`, + S{ID: "root", Recurse: &S{ID: "level2", Name: "Level 2"}}) + s.TestOK(t, "3_level", + `{ + "id": "root", + "recurse": { + "id": "level2", + "recurse": { + "id": "level3" + } + } + }`, + S{ID: "root", Recurse: &S{ID: "level2", Recurse: &S{ID: "level3"}}}) + s.TestOK(t, "3_level_reversed_field_order", + `{ + "recurse": { + "recurse": { + "id": "level3" + }, + "id": "level2" + }, + "id": "root" + }`, + S{ID: "root", Recurse: &S{ID: "level2", Recurse: &S{ID: "level3"}}}) + s.TestOK(t, "3_level_missing_field", + `{ + "recurse": { + "recurse": {} + } + }`, + S{Recurse: &S{Recurse: &S{}}}) + s.TestOK(t, "null_level2", + `{ + "id": "root", + "recurse": null + }`, + S{ID: "root", Recurse: nil}) + s.TestOK(t, "empty_level2", + `{ + "id": "root", + "recurse": {} + }`, + S{ID: "root", Recurse: &S{}}) + s.TestOK(t, "null_level3", + `{ + "id": "root", + "recurse": { + "id":"level2", + "recurse": null + } + }`, + S{ID: "root", Recurse: &S{ID: "level2", Recurse: nil}}) + s.TestOK(t, "empty_level3", + `{ + "id": "root", + "recurse": { + "id":"level2", + "recurse": {} + } + }`, + S{ID: "root", Recurse: &S{ID: "level2", Recurse: &S{}}}) + s.TestOK(t, "3_level_unknown_field", + `{ + "recurse": { + "recurse": { + "unknown":["okay"] + } + } + }`, + S{Recurse: &S{Recurse: &S{}}}) + s.TestOK(t, "3_level_upper_case_field_names", + `{ + "RECURSE": { + "RECURSE": { + "ID": "level3" + }, + "ID": "level2" + }, + "ID": "root" + }`, + S{ID: "root", Recurse: &S{ID: "level2", Recurse: &S{ID: "level3"}}}) + + s.TestOKPrepare(t, "overwrite", `{"name":"Root", "recurse":{"name": "L2"}}`, + func() S { return S{ID: "root", Recurse: &S{ID: "level2"}} }, + S{ID: "root", Name: "Root", Recurse: &S{ID: "level2", Name: "L2"}}) + + s.TestOKPrepare(t, "overwrite_null_level2", `{"name":"Root", "recurse":null}`, + func() S { return S{ID: "root", Recurse: &S{ID: "level2"}} }, + S{ID: "root", Name: "Root", Recurse: nil}) + + s.TestOKPrepare(t, "overwrite_empty_level2", `{"name":"Root", "recurse":{}}`, + func() S { return S{ID: "root", Recurse: &S{ID: "level2"}} }, + S{ID: "root", Name: "Root", Recurse: &S{ID: "level2"}}) + s.TestOKPrepare(t, "overwrite_unknown_field_level2", + `{"name":"Root", "recurse":{"fuzz":"inexistent"}}`, + func() S { return S{ID: "root", Recurse: &S{ID: "level2"}} }, + S{ID: "root", Name: "Root", Recurse: &S{ID: "level2"}}) + + s.testErr(t, "wrong_type_level2", `{"id":"root","recurse":[]}`, + jscandec.ErrorDecode{Err: jscandec.ErrUnexpectedValue, Index: 23}) + s.testErr(t, "wrong_type_level3", `{"id":"root","recurse":{"id":"2","recurse":"x"}}`, + jscandec.ErrorDecode{Err: jscandec.ErrUnexpectedValue, Index: 43}) + s.testErr(t, "int", `1`, + jscandec.ErrorDecode{Err: jscandec.ErrUnexpectedValue, Index: 0}) + s.testErr(t, "array", `[]`, + jscandec.ErrorDecode{Err: jscandec.ErrUnexpectedValue, Index: 0}) + s.testErr(t, "string", `"text"`, + jscandec.ErrorDecode{Err: jscandec.ErrUnexpectedValue, Index: 0}) +} + func TestDecodeStructErrUknownField(t *testing.T) { type S struct { Foo int `json:"foo"`