Deconvolute optional fields writer (#77)
This commit is contained in:
parent
42253591b9
commit
b14f5b58e9
|
@ -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
|
||||
|
|
|
@ -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]) =
|
||||
|
|
|
@ -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]) =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}"""
|
||||
|
|
Loading…
Reference in New Issue