From 9c74b885eaeb6726543e69fde2f7d8837357c1d7 Mon Sep 17 00:00:00 2001 From: jangko Date: Thu, 11 Jan 2024 16:51:00 +0700 Subject: [PATCH] Writer produce correct top-level or in-array optional elem when custom flavor omit optional fields --- json_serialization/std/options.nim | 3 +- json_serialization/stew/results.nim | 3 +- json_serialization/writer.nim | 19 +++++- tests/test_all.nim | 3 +- tests/test_writer.nim | 101 ++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 tests/test_writer.nim diff --git a/json_serialization/std/options.nim b/json_serialization/std/options.nim index e816615..b5e4353 100644 --- a/json_serialization/std/options.nim +++ b/json_serialization/std/options.nim @@ -27,7 +27,8 @@ proc writeValue*(writer: var JsonWriter, value: Option) {.raises: [IOError].} = if value.isSome: writer.writeValue value.get - elif not flavorOmitsOptionalFields(Flavor): + elif not flavorOmitsOptionalFields(Flavor) or + writer.nesting != JsonNesting.WriteObject: writer.writeValue JsonString("null") proc readValue*[T](reader: var JsonReader, value: var Option[T]) = diff --git a/json_serialization/stew/results.nim b/json_serialization/stew/results.nim index 0a8e4d0..baa54ef 100644 --- a/json_serialization/stew/results.nim +++ b/json_serialization/stew/results.nim @@ -31,7 +31,8 @@ proc writeValue*[T]( if value.isOk: writer.writeValue value.get - elif not flavorOmitsOptionalFields(Flavor): + elif not flavorOmitsOptionalFields(Flavor) or + writer.nesting != JsonNesting.WriteObject: writer.writeValue JsonString("null") proc readValue*[T](reader: var JsonReader, value: var Result[T, void]) = diff --git a/json_serialization/writer.nim b/json_serialization/writer.nim index a72d4e6..6591af2 100644 --- a/json_serialization/writer.nim +++ b/json_serialization/writer.nim @@ -22,12 +22,19 @@ type RecordStarted AfterField + JsonNesting* {.pure.} = enum + TopLevel + WriteObject + WriteArray + JsonWriter*[Flavor = DefaultFlavor] = object stream*: OutputStream hasTypeAnnotations: bool hasPrettyOutput*: bool # read-only nestingLevel*: int # read-only state: JsonWriterState + nesting: JsonNesting + prevNesting: seq[JsonNesting] Json.setWriter JsonWriter, PreferredOutput = string @@ -38,7 +45,11 @@ func init*(W: type JsonWriter, stream: OutputStream, hasPrettyOutput: pretty, hasTypeAnnotations: typeAnnotations, nestingLevel: if pretty: 0 else: -1, - state: RecordExpected) + state: RecordExpected, + nesting: JsonNesting.TopLevel) + +func nesting*(w: JsonWriter): JsonNesting = + w.nesting proc beginRecord*(w: var JsonWriter, T: type) proc beginRecord*(w: var JsonWriter) @@ -90,6 +101,8 @@ template fieldWritten*(w: var JsonWriter) = proc beginRecord*(w: var JsonWriter) = doAssert w.state == RecordExpected + w.prevNesting.add w.nesting + w.nesting = JsonNesting.WriteObject append '{' if w.hasPrettyOutput: w.nestingLevel += 2 @@ -109,12 +122,15 @@ proc endRecord*(w: var JsonWriter) = indent() append '}' + w.nesting = w.prevNesting.pop() template endRecordField*(w: var JsonWriter) = endRecord(w) w.state = AfterField iterator stepwiseArrayCreation*[C](w: var JsonWriter, collection: C): auto = + w.prevNesting.add w.nesting + w.nesting = JsonNesting.WriteArray append '[' if w.hasPrettyOutput: @@ -140,6 +156,7 @@ iterator stepwiseArrayCreation*[C](w: var JsonWriter, collection: C): auto = indent() append ']' + w.nesting = w.prevNesting.pop() proc writeIterable*(w: var JsonWriter, collection: auto) = mixin writeValue diff --git a/tests/test_all.nim b/tests/test_all.nim index 3d07223..07d0d6a 100644 --- a/tests/test_all.nim +++ b/tests/test_all.nim @@ -16,4 +16,5 @@ import test_spec, test_parser, test_line_col, - test_reader + test_reader, + test_writer diff --git a/tests/test_writer.nim b/tests/test_writer.nim new file mode 100644 index 0000000..2ebe0d8 --- /dev/null +++ b/tests/test_writer.nim @@ -0,0 +1,101 @@ +# json-serialization +# Copyright (c) 2024 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. + +import + unittest2, + ../json_serialization/stew/results, + ../json_serialization/std/options, + ../json_serialization + +createJsonFlavor YourJson, + omitOptionalFields = false + +createJsonFlavor MyJson, + omitOptionalFields = true + +suite "Test writer": + test "stdlib option top level some YourJson": + var val = some(123) + let json = YourJson.encode(val) + check json == "123" + + test "stdlib option top level none YourJson": + var val = none(int) + let json = YourJson.encode(val) + check json == "null" + + test "stdlib option top level some MyJson": + var val = some(123) + let json = MyJson.encode(val) + check json == "123" + + test "stdlib option top level none MyJson": + var val = none(int) + let json = MyJson.encode(val) + check json == "null" + + test "results option top level some YourJson": + var val = Opt.some(123) + let json = YourJson.encode(val) + check json == "123" + + test "results option top level none YourJson": + var val = Opt.none(int) + let json = YourJson.encode(val) + check json == "null" + + test "results option top level some MyJson": + var val = Opt.some(123) + let json = MyJson.encode(val) + check json == "123" + + test "results option top level none MyJson": + var val = Opt.none(int) + let json = MyJson.encode(val) + check json == "null" + + test "stdlib option array some YourJson": + var val = [some(123), some(345)] + let json = YourJson.encode(val) + check json == "[123,345]" + + test "stdlib option array none YourJson": + var val = [some(123), none(int), some(777)] + let json = YourJson.encode(val) + check json == "[123,null,777]" + + test "stdlib option array some MyJson": + var val = [some(123), some(345)] + let json = MyJson.encode(val) + check json == "[123,345]" + + test "stdlib option array none MyJson": + var val = [some(123), none(int), some(777)] + let json = MyJson.encode(val) + check json == "[123,null,777]" + + test "results option array some YourJson": + var val = [Opt.some(123), Opt.some(345)] + let json = YourJson.encode(val) + check json == "[123,345]" + + test "results option array none YourJson": + var val = [Opt.some(123), Opt.none(int), Opt.some(777)] + let json = YourJson.encode(val) + check json == "[123,null,777]" + + test "results option array some MyJson": + var val = [Opt.some(123), Opt.some(345)] + let json = MyJson.encode(val) + check json == "[123,345]" + + test "results option array none MyJson": + var val = [Opt.some(123), Opt.none(int), Opt.some(777)] + let json = MyJson.encode(val) + check json == "[123,null,777]"