goccy / go-json

Fast JSON encoder/decoder compatible with encoding/json for Go
MIT License
3.12k stars 148 forks source link

BUG: Empty encode result in case of zero first byte in child struct as pointer with omitempty tag #503

Open JekaNS opened 8 months ago

JekaNS commented 8 months ago

Encoder return empty json object "{}" In case of encoding struct that has a child as pointer with omitempty tag and this child has first field with value of zero byte (false or 0 or empty string)

Here is the test cases to reproduce:

package jsontest

import (
    "testing"

    "github.com/goccy/go-json"
    "github.com/stretchr/testify/assert"
)

type RootInt struct {
    Child *ChildInt `json:"c,omitempty"`
}

type ChildInt struct {
    Value int `json:"v"`
}

type RootBool struct {
    Child *ChildBool `json:"c,omitempty"`
}

type ChildBool struct {
    Value bool `json:"v"`
}

type RootString struct {
    Child *ChildString `json:"c,omitempty"`
}

type ChildString struct {
    Value string `json:"v"`
}

func TestZeroFirstByteAfterPtrInt(t *testing.T) {
    obj := RootInt{Child: &ChildInt{Value: 0}}

    jsonDst, e2 := json.Marshal(obj)

    assert.NoError(t, e2)
    assert.Equal(t, `{"c":{"v":0}}`, string(jsonDst))
}

func TestNonZeroFirstByteAfterPtrInt(t *testing.T) {
    obj := RootInt{Child: &ChildInt{Value: 1}}

    jsonDst, e2 := json.Marshal(obj)

    assert.NoError(t, e2)
    assert.Equal(t, `{"c":{"v":1}}`, string(jsonDst))
}

func TestZeroFirstByteAfterPtrBool(t *testing.T) {
    obj := RootBool{Child: &ChildBool{Value: false}}

    jsonDst, e2 := json.Marshal(obj)

    assert.NoError(t, e2)
    assert.Equal(t, `{"c":{"v":false}}`, string(jsonDst))
}

func TestNonZeroFirstByteAfterPtrBool(t *testing.T) {
    obj := RootBool{Child: &ChildBool{Value: true}}

    jsonDst, e2 := json.Marshal(obj)

    assert.NoError(t, e2)
    assert.Equal(t, `{"c":{"v":true}}`, string(jsonDst))
}

func TestZeroFirstByteAfterPtrString(t *testing.T) {
    obj := RootString{Child: &ChildString{Value: ""}}

    jsonDst, e2 := json.Marshal(obj)

    assert.NoError(t, e2)
    assert.Equal(t, `{"c":{"v":""}}`, string(jsonDst))
}

func TestNonZeroFirstByteAfterPtrString(t *testing.T) {
    obj := RootString{Child: &ChildString{Value: "a"}}

    jsonDst, e2 := json.Marshal(obj)

    assert.NoError(t, e2)
    assert.Equal(t, `{"c":{"v":"a"}}`, string(jsonDst))
}

Tests run result:

=== RUN   TestZeroFirstByteAfterPtrInt
    json_bug_test.go:40: 
            Error Trace:    /Users/jeka/Veeam/workbench/projects/cast/internal/rm/casedata/json_bug_test.go:40
            Error:          Not equal: 
                            expected: "{\"c\":{\"v\":0}}"
                            actual  : "{}"

                            Diff:
                            --- Expected
                            +++ Actual
                            @@ -1 +1 @@
                            -{"c":{"v":0}}
                            +{}
            Test:           TestZeroFirstByteAfterPtrInt
--- FAIL: TestZeroFirstByteAfterPtrInt (0.00s)

Expected :{"c":{"v":0}}
Actual   :{}
<Click to see difference>

=== RUN   TestNonZeroFirstByteAfterPtrInt
--- PASS: TestNonZeroFirstByteAfterPtrInt (0.00s)
=== RUN   TestZeroFirstByteAfterPtrBool
    json_bug_test.go:58: 
            Error Trace:    /Users/jeka/Veeam/workbench/projects/cast/internal/rm/casedata/json_bug_test.go:58
            Error:          Not equal: 
                            expected: "{\"c\":{\"v\":false}}"
                            actual  : "{}"

                            Diff:
                            --- Expected
                            +++ Actual
                            @@ -1 +1 @@
                            -{"c":{"v":false}}
                            +{}
            Test:           TestZeroFirstByteAfterPtrBool
--- FAIL: TestZeroFirstByteAfterPtrBool (0.00s)

Expected :{"c":{"v":false}}
Actual   :{}
<Click to see difference>

=== RUN   TestNonZeroFirstByteAfterPtrBool
--- PASS: TestNonZeroFirstByteAfterPtrBool (0.00s)
=== RUN   TestZeroFirstByteAfterPtrString
    json_bug_test.go:76: 
            Error Trace:    /Users/jeka/Veeam/workbench/projects/cast/internal/rm/casedata/json_bug_test.go:76
            Error:          Not equal: 
                            expected: "{\"c\":{\"v\":\"\"}}"
                            actual  : "{}"

                            Diff:
                            --- Expected
                            +++ Actual
                            @@ -1 +1 @@
                            -{"c":{"v":""}}
                            +{}
            Test:           TestZeroFirstByteAfterPtrString
--- FAIL: TestZeroFirstByteAfterPtrString (0.00s)

Expected :{"c":{"v":""}}
Actual   :{}
<Click to see difference>

=== RUN   TestNonZeroFirstByteAfterPtrString
--- PASS: TestNonZeroFirstByteAfterPtrString (0.00s)
FAIL

I thik problem is here https://github.com/goccy/go-json/blob/df897aec9dc4228e585e8127b4db026d506d2b3c/internal/encoder/vm/vm.go#L585 ptrToPtr(p) in this case return not a pointer but value

JekaNS commented 8 months ago

Same bug with empty string. I've updated the test code and result in the first post.

Also I tried this on different architectures. Bug is reproduced on all of them: