From 4cb0c9b28ff7490b1d4392a956029b805f5534e0 Mon Sep 17 00:00:00 2001 From: w1ck3dg0ph3r Date: Thu, 1 Sep 2022 10:42:38 +0400 Subject: [PATCH] Add NilSliceAsEmpty encode option --- encode_test.go | 24 ++++++++++++++++++++++++ internal/cmd/generator/vm.go.tmpl | 14 ++++++++++++-- internal/encoder/option.go | 3 ++- internal/encoder/vm/vm.go | 14 ++++++++++++-- internal/encoder/vm_color/vm.go | 14 ++++++++++++-- internal/encoder/vm_color_indent/vm.go | 14 ++++++++++++-- internal/encoder/vm_indent/vm.go | 14 ++++++++++++-- option.go | 7 +++++++ 8 files changed, 93 insertions(+), 11 deletions(-) diff --git a/encode_test.go b/encode_test.go index b2cbceb7..adeca5bd 100644 --- a/encode_test.go +++ b/encode_test.go @@ -426,6 +426,21 @@ func Test_Marshal(t *testing.T) { assertErr(t, err) assertEq(t, "[]interface{}", `[1,2.1,"hello"]`, string(bytes)) }) + t.Run("nil_slice", func(t *testing.T) { + var a []int + bytes, err := json.Marshal(a) + assertErr(t, err) + assertEq(t, "nil_slice", `null`, string(bytes)) + bytes, err = json.Marshal(&a) + assertErr(t, err) + assertEq(t, "nil_slice_ptr", `null`, string(bytes)) + bytes, err = json.MarshalWithOption(a, json.NilSliceAsEmpty()) + assertErr(t, err) + assertEq(t, "nil_slice_as_empty", `[]`, string(bytes)) + bytes, err = json.MarshalWithOption(&a, json.NilSliceAsEmpty()) + assertErr(t, err) + assertEq(t, "nil_slice_ptr_as_empty", `[]`, string(bytes)) + }) }) t.Run("array", func(t *testing.T) { @@ -590,6 +605,15 @@ func Test_MarshalIndent(t *testing.T) { result := "[\n-\t1,\n-\t2.1,\n-\t\"hello\"\n-]" assertEq(t, "[]interface{}", result, string(bytes)) }) + t.Run("nil", func(t *testing.T) { + var a []int + bytes, err := json.MarshalIndent(a, prefix, indent) + assertErr(t, err) + assertEq(t, "nil_slice", `null`, string(bytes)) + bytes, err = json.MarshalIndentWithOption(a, prefix, indent, json.NilSliceAsEmpty()) + assertErr(t, err) + assertEq(t, "nil_slice_as_empty", `[]`, string(bytes)) + }) }) t.Run("array", func(t *testing.T) { diff --git a/internal/cmd/generator/vm.go.tmpl b/internal/cmd/generator/vm.go.tmpl index 645d20f9..038d1644 100644 --- a/internal/cmd/generator/vm.go.tmpl +++ b/internal/cmd/generator/vm.go.tmpl @@ -301,19 +301,29 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b b = appendComma(ctx, bb) code = code.Next case encoder.OpSlicePtr: + nilSliceAsEmpty := ctx.Option.Flag & encoder.NilSliceAsEmptyOption != 0 p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpSlice: + nilSliceAsEmpty := ctx.Option.Flag & encoder.NilSliceAsEmptyOption != 0 p := load(ctxptr, code.Idx) slice := ptrToSlice(p) if p == 0 || slice.Data == nil { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } diff --git a/internal/encoder/option.go b/internal/encoder/option.go index 82d5ce3e..672fe4ac 100644 --- a/internal/encoder/option.go +++ b/internal/encoder/option.go @@ -5,12 +5,13 @@ import ( "io" ) -type OptionFlag uint8 +type OptionFlag uint16 const ( HTMLEscapeOption OptionFlag = 1 << iota IndentOption UnorderedMapOption + NilSliceAsEmptyOption DebugOption ColorizeOption ContextOption diff --git a/internal/encoder/vm/vm.go b/internal/encoder/vm/vm.go index 645d20f9..4d54dcd8 100644 --- a/internal/encoder/vm/vm.go +++ b/internal/encoder/vm/vm.go @@ -301,19 +301,29 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b b = appendComma(ctx, bb) code = code.Next case encoder.OpSlicePtr: + nilSliceAsEmpty := ctx.Option.Flag&encoder.NilSliceAsEmptyOption != 0 p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpSlice: + nilSliceAsEmpty := ctx.Option.Flag&encoder.NilSliceAsEmptyOption != 0 p := load(ctxptr, code.Idx) slice := ptrToSlice(p) if p == 0 || slice.Data == nil { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } diff --git a/internal/encoder/vm_color/vm.go b/internal/encoder/vm_color/vm.go index a63e83e5..a112d560 100644 --- a/internal/encoder/vm_color/vm.go +++ b/internal/encoder/vm_color/vm.go @@ -301,19 +301,29 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b b = appendComma(ctx, bb) code = code.Next case encoder.OpSlicePtr: + nilSliceAsEmpty := ctx.Option.Flag&encoder.NilSliceAsEmptyOption != 0 p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpSlice: + nilSliceAsEmpty := ctx.Option.Flag&encoder.NilSliceAsEmptyOption != 0 p := load(ctxptr, code.Idx) slice := ptrToSlice(p) if p == 0 || slice.Data == nil { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } diff --git a/internal/encoder/vm_color_indent/vm.go b/internal/encoder/vm_color_indent/vm.go index 3b4e22e5..e62970f9 100644 --- a/internal/encoder/vm_color_indent/vm.go +++ b/internal/encoder/vm_color_indent/vm.go @@ -301,19 +301,29 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b b = appendComma(ctx, bb) code = code.Next case encoder.OpSlicePtr: + nilSliceAsEmpty := ctx.Option.Flag&encoder.NilSliceAsEmptyOption != 0 p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpSlice: + nilSliceAsEmpty := ctx.Option.Flag&encoder.NilSliceAsEmptyOption != 0 p := load(ctxptr, code.Idx) slice := ptrToSlice(p) if p == 0 || slice.Data == nil { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } diff --git a/internal/encoder/vm_indent/vm.go b/internal/encoder/vm_indent/vm.go index 836c5c8a..59662853 100644 --- a/internal/encoder/vm_indent/vm.go +++ b/internal/encoder/vm_indent/vm.go @@ -301,19 +301,29 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet) ([]b b = appendComma(ctx, bb) code = code.Next case encoder.OpSlicePtr: + nilSliceAsEmpty := ctx.Option.Flag&encoder.NilSliceAsEmptyOption != 0 p := loadNPtr(ctxptr, code.Idx, code.PtrNum) if p == 0 { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } store(ctxptr, code.Idx, p) fallthrough case encoder.OpSlice: + nilSliceAsEmpty := ctx.Option.Flag&encoder.NilSliceAsEmptyOption != 0 p := load(ctxptr, code.Idx) slice := ptrToSlice(p) if p == 0 || slice.Data == nil { - b = appendNullComma(ctx, b) + if nilSliceAsEmpty { + b = appendEmptyArray(ctx, b) + } else { + b = appendNullComma(ctx, b) + } code = code.End.Next break } diff --git a/option.go b/option.go index af400a45..e725e66e 100644 --- a/option.go +++ b/option.go @@ -17,6 +17,13 @@ func UnorderedMap() EncodeOptionFunc { } } +// NilSliceAsEmpty encodes nil slices as [] instead of null. +func NilSliceAsEmpty() EncodeOptionFunc { + return func(opt *EncodeOption) { + opt.Flag |= encoder.NilSliceAsEmptyOption + } +} + // DisableHTMLEscape disables escaping of HTML characters ( '&', '<', '>' ) when encoding string. func DisableHTMLEscape() EncodeOptionFunc { return func(opt *EncodeOption) {