Deconvolute optional fields writer (#77)

This commit is contained in:
andri lim 2024-01-17 07:48:42 +07:00 committed by GitHub
parent 42253591b9
commit b14f5b58e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 63 additions and 53 deletions

View File

@ -21,7 +21,7 @@ template supports*(_: type Json, T: type): bool =
true
template flavorUsesAutomaticObjectSerialization*(T: type DefaultFlavor): bool = true
template flavorOmitsOptionalFields*(T: type DefaultFlavor): bool = false
template flavorOmitsOptionalFields*(T: type DefaultFlavor): bool = true
template flavorRequiresAllFields*(T: type DefaultFlavor): bool = false
template flavorAllowsUnknownFields*(T: type DefaultFlavor): bool = false
template flavorSkipNullFields*(T: type DefaultFlavor): bool = false

View File

@ -10,25 +10,15 @@
import std/options, ../../json_serialization/[reader, writer, lexer]
export options
template writeObjectField*(w: var JsonWriter,
record: auto,
fieldName: static string,
field: Option): bool =
mixin writeObjectField
if field.isSome:
writeObjectField(w, record, fieldName, field.get)
else:
false
template shouldWriteObjectField*(field: Option): bool =
field.isSome
proc writeValue*(writer: var JsonWriter, value: Option) {.raises: [IOError].} =
mixin writeValue, flavorOmitsOptionalFields
type Flavor = JsonWriter.Flavor
mixin writeValue
if value.isSome:
writer.writeValue value.get
elif not flavorOmitsOptionalFields(Flavor) or
writer.nesting != JsonNesting.WriteObject:
else:
writer.writeValue JsonString("null")
proc readValue*[T](reader: var JsonReader, value: var Option[T]) =

View File

@ -13,26 +13,16 @@ import
export
results
template writeObjectField*[T](w: var JsonWriter,
record: auto,
fieldName: static string,
field: Result[T, void]): bool =
mixin writeObjectField
if field.isOk:
writeObjectField(w, record, fieldName, field.get)
else:
false
template shouldWriteObjectField*[T](field: Result[T, void]): bool =
field.isOk
proc writeValue*[T](
writer: var JsonWriter, value: Result[T, void]) {.raises: [IOError].} =
mixin writeValue, flavorOmitsOptionalFields
type Flavor = JsonWriter.Flavor
mixin writeValue
if value.isOk:
writer.writeValue value.get
elif not flavorOmitsOptionalFields(Flavor) or
writer.nesting != JsonNesting.WriteObject:
else:
writer.writeValue JsonString("null")
proc readValue*[T](reader: var JsonReader, value: var Result[T, void]) =

View File

@ -22,19 +22,12 @@ 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
@ -45,11 +38,7 @@ func init*(W: type JsonWriter, stream: OutputStream,
hasPrettyOutput: pretty,
hasTypeAnnotations: typeAnnotations,
nestingLevel: if pretty: 0 else: -1,
state: RecordExpected,
nesting: JsonNesting.TopLevel)
func nesting*(w: JsonWriter): JsonNesting =
w.nesting
state: RecordExpected)
proc beginRecord*(w: var JsonWriter, T: type)
proc beginRecord*(w: var JsonWriter)
@ -101,8 +90,6 @@ 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
@ -122,15 +109,12 @@ 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:
@ -156,7 +140,6 @@ 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
@ -173,10 +156,14 @@ template isStringLike(v: string|cstring|openArray[char]|seq[char]): bool = true
template isStringLike[N](v: array[N, char]): bool = true
template isStringLike(v: auto): bool = false
# If it's an optional field, test for it's value before write something.
# If it's non optional field, the field is always written.
template shouldWriteObjectField*[FieldType](field: FieldType): bool = true
template writeObjectField*[FieldType, RecordType](w: var JsonWriter,
record: RecordType,
fieldName: static string,
field: FieldType): bool =
field: FieldType) =
mixin writeFieldIMPL, writeValue
w.writeFieldName(fieldName)
@ -185,17 +172,26 @@ template writeObjectField*[FieldType, RecordType](w: var JsonWriter,
else:
type R = type record
w.writeFieldIMPL(FieldTag[R, fieldName], field, record)
true
proc writeRecordValue*(w: var JsonWriter, value: auto)
{.gcsafe, raises: [IOError].} =
mixin enumInstanceSerializedFields, writeObjectField
mixin flavorOmitsOptionalFields, shouldWriteObjectField
type
Writer = typeof w
Flavor = Writer.Flavor
type RecordType = type value
w.beginRecord RecordType
value.enumInstanceSerializedFields(fieldName, fieldType):
when fieldType isnot JsonVoid:
if writeObjectField(w, value, fieldName, fieldType):
value.enumInstanceSerializedFields(fieldName, fieldValue):
when fieldValue isnot JsonVoid:
when flavorOmitsOptionalFields(Flavor):
if shouldWriteObjectField(fieldValue):
writeObjectField(w, value, fieldName, fieldValue)
w.state = AfterField
else:
writeObjectField(w, value, fieldName, fieldValue)
w.state = AfterField
else:
discard fieldName

View File

@ -988,7 +988,7 @@ suite "Custom parser tests":
check dData.name == "FancyUInt"
check dData.data.uint == 12345u
check customVisit.entry == JsonValueKind.String
test "Parser on text blob with embedded quote (backlash escape support)":
customVisit = TokenRegistry.default

View File

@ -13,12 +13,21 @@ import
../json_serialization/std/options,
../json_serialization
type
ObjectWithOptionalFields = object
a: Opt[int]
b: Option[string]
c: int
createJsonFlavor YourJson,
omitOptionalFields = false
createJsonFlavor MyJson,
omitOptionalFields = true
ObjectWithOptionalFields.useDefaultSerializationIn YourJson
ObjectWithOptionalFields.useDefaultSerializationIn MyJson
suite "Test writer":
test "stdlib option top level some YourJson":
var val = some(123)
@ -99,3 +108,28 @@ suite "Test writer":
var val = [Opt.some(123), Opt.none(int), Opt.some(777)]
let json = MyJson.encode(val)
check json == "[123,null,777]"
test "object with optional fields":
let x = ObjectWithOptionalFields(
a: Opt.some(123),
b: some("nano"),
c: 456,
)
let y = ObjectWithOptionalFields(
a: Opt.none(int),
b: none(string),
c: 999,
)
let u = YourJson.encode(x)
check u.string == """{"a":123,"b":"nano","c":456}"""
let v = YourJson.encode(y)
check v.string == """{"a":null,"b":null,"c":999}"""
let xx = MyJson.encode(x)
check xx.string == """{"a":123,"b":"nano","c":456}"""
let yy = MyJson.encode(y)
check yy.string == """{"c":999}"""