From ae909aeb7242f3c26b4c5870821ab8abe35ce5a1 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 11 Oct 2025 13:08:06 +0800 Subject: [PATCH] jsontext: add support for encoding null (#5371) --- qlogwriter/jsontext/encoder.go | 10 ++++++++++ qlogwriter/jsontext/encoder_test.go | 24 +++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/qlogwriter/jsontext/encoder.go b/qlogwriter/jsontext/encoder.go index b838e2fb..4f715bbd 100644 --- a/qlogwriter/jsontext/encoder.go +++ b/qlogwriter/jsontext/encoder.go @@ -19,6 +19,7 @@ const ( kindUint kindFloat kindBool + kindNull kindObjectStart kindObjectEnd kindArrayStart @@ -60,6 +61,9 @@ func Bool(b bool) Token { return Token{kind: kindBool, b: b} } +// Null is a null token. +var Null Token = Token{kind: kindNull} + // BeginObject is the begin object token. var BeginObject Token = Token{kind: kindObjectStart} @@ -86,6 +90,7 @@ var ( colonByte = []byte(":") trueByte = []byte("true") falseByte = []byte("false") + nullByte = []byte("null") openObjectByte = []byte("{") closeObjectByte = []byte("}") openArrayByte = []byte("[") @@ -256,6 +261,11 @@ func (e *Encoder) WriteToken(t Token) error { } } e.afterValue() + case kindNull: + if _, err = e.w.Write(nullByte); err != nil { + return err + } + e.afterValue() case kindObjectStart: if _, err = e.w.Write(openObjectByte); err != nil { return err diff --git a/qlogwriter/jsontext/encoder_test.go b/qlogwriter/jsontext/encoder_test.go index d684e994..f580cfaa 100644 --- a/qlogwriter/jsontext/encoder_test.go +++ b/qlogwriter/jsontext/encoder_test.go @@ -88,6 +88,8 @@ func TestEncoderNumbersAndBool(t *testing.T) { enc.WriteToken(jsontext.True) enc.WriteToken(jsontext.String("false")) enc.WriteToken(jsontext.False) + enc.WriteToken(jsontext.String("nullv")) + enc.WriteToken(jsontext.Null) enc.WriteToken(jsontext.EndObject) output := buf.String() @@ -99,6 +101,7 @@ func TestEncoderNumbersAndBool(t *testing.T) { "float": 3.14, "true": true, "false": false, + "nullv": nil, }, got) } @@ -126,6 +129,21 @@ func TestEncoderEmptyArray(t *testing.T) { require.Equal(t, []any{}, got) } +func TestEncoderArrayWithNulls(t *testing.T) { + buf := bytes.NewBuffer(nil) + enc := jsontext.NewEncoder(buf) + enc.WriteToken(jsontext.BeginArray) + enc.WriteToken(jsontext.Null) + enc.WriteToken(jsontext.String("x")) + enc.WriteToken(jsontext.Null) + enc.WriteToken(jsontext.EndArray) + output := buf.String() + + var got []any + require.NoError(t, json.Unmarshal([]byte(output), &got)) + require.Equal(t, []any{nil, "x", nil}, got) +} + func TestEncoderEscapedStrings(t *testing.T) { t.Run("no escapes", func(t *testing.T) { testEncoderEscapedStrings(t, "simplekey", "simplevalue") @@ -210,6 +228,9 @@ func encodeValue(t testing.TB, enc *jsontext.Encoder, v any) (isSupported bool) case bool: require.NoError(t, enc.WriteToken(jsontext.Bool(val))) return true + case nil: + require.NoError(t, enc.WriteToken(jsontext.Null)) + return true default: return false } @@ -326,12 +347,13 @@ func FuzzEncoder(f *testing.F) { examples := []string{ `{"hello": "world"}`, `{"foo": 123, "bar": [1, 2, 3]}`, - `{"nested": {"a": 1, "b": [true, false, "foobar"]}}`, + `{"nested": {"a": 1, "b": [true, false, "foobar", null]}}`, `[{"x": 1}, {"y": "foo"}]`, `["foo", "bar"]`, `["a", {"b": [1, 2, {"c": "d"}]}, 3]`, `{"emptyObj": {}, "emptyArr": []}`, `{"mixed": [1, "two", {"three": 3}]}`, + `[null]`, } for _, tc := range examples { // first test that