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
|
true
|
||||||
|
|
||||||
template flavorUsesAutomaticObjectSerialization*(T: type DefaultFlavor): 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 flavorRequiresAllFields*(T: type DefaultFlavor): bool = false
|
||||||
template flavorAllowsUnknownFields*(T: type DefaultFlavor): bool = false
|
template flavorAllowsUnknownFields*(T: type DefaultFlavor): bool = false
|
||||||
template flavorSkipNullFields*(T: type DefaultFlavor): bool = false
|
template flavorSkipNullFields*(T: type DefaultFlavor): bool = false
|
||||||
|
|
|
@ -10,25 +10,15 @@
|
||||||
import std/options, ../../json_serialization/[reader, writer, lexer]
|
import std/options, ../../json_serialization/[reader, writer, lexer]
|
||||||
export options
|
export options
|
||||||
|
|
||||||
template writeObjectField*(w: var JsonWriter,
|
template shouldWriteObjectField*(field: Option): bool =
|
||||||
record: auto,
|
field.isSome
|
||||||
fieldName: static string,
|
|
||||||
field: Option): bool =
|
|
||||||
mixin writeObjectField
|
|
||||||
|
|
||||||
if field.isSome:
|
|
||||||
writeObjectField(w, record, fieldName, field.get)
|
|
||||||
else:
|
|
||||||
false
|
|
||||||
|
|
||||||
proc writeValue*(writer: var JsonWriter, value: Option) {.raises: [IOError].} =
|
proc writeValue*(writer: var JsonWriter, value: Option) {.raises: [IOError].} =
|
||||||
mixin writeValue, flavorOmitsOptionalFields
|
mixin writeValue
|
||||||
type Flavor = JsonWriter.Flavor
|
|
||||||
|
|
||||||
if value.isSome:
|
if value.isSome:
|
||||||
writer.writeValue value.get
|
writer.writeValue value.get
|
||||||
elif not flavorOmitsOptionalFields(Flavor) or
|
else:
|
||||||
writer.nesting != JsonNesting.WriteObject:
|
|
||||||
writer.writeValue JsonString("null")
|
writer.writeValue JsonString("null")
|
||||||
|
|
||||||
proc readValue*[T](reader: var JsonReader, value: var Option[T]) =
|
proc readValue*[T](reader: var JsonReader, value: var Option[T]) =
|
||||||
|
|
|
@ -13,26 +13,16 @@ import
|
||||||
export
|
export
|
||||||
results
|
results
|
||||||
|
|
||||||
template writeObjectField*[T](w: var JsonWriter,
|
template shouldWriteObjectField*[T](field: Result[T, void]): bool =
|
||||||
record: auto,
|
field.isOk
|
||||||
fieldName: static string,
|
|
||||||
field: Result[T, void]): bool =
|
|
||||||
mixin writeObjectField
|
|
||||||
|
|
||||||
if field.isOk:
|
|
||||||
writeObjectField(w, record, fieldName, field.get)
|
|
||||||
else:
|
|
||||||
false
|
|
||||||
|
|
||||||
proc writeValue*[T](
|
proc writeValue*[T](
|
||||||
writer: var JsonWriter, value: Result[T, void]) {.raises: [IOError].} =
|
writer: var JsonWriter, value: Result[T, void]) {.raises: [IOError].} =
|
||||||
mixin writeValue, flavorOmitsOptionalFields
|
mixin writeValue
|
||||||
type Flavor = JsonWriter.Flavor
|
|
||||||
|
|
||||||
if value.isOk:
|
if value.isOk:
|
||||||
writer.writeValue value.get
|
writer.writeValue value.get
|
||||||
elif not flavorOmitsOptionalFields(Flavor) or
|
else:
|
||||||
writer.nesting != JsonNesting.WriteObject:
|
|
||||||
writer.writeValue JsonString("null")
|
writer.writeValue JsonString("null")
|
||||||
|
|
||||||
proc readValue*[T](reader: var JsonReader, value: var Result[T, void]) =
|
proc readValue*[T](reader: var JsonReader, value: var Result[T, void]) =
|
||||||
|
|
|
@ -22,19 +22,12 @@ type
|
||||||
RecordStarted
|
RecordStarted
|
||||||
AfterField
|
AfterField
|
||||||
|
|
||||||
JsonNesting* {.pure.} = enum
|
|
||||||
TopLevel
|
|
||||||
WriteObject
|
|
||||||
WriteArray
|
|
||||||
|
|
||||||
JsonWriter*[Flavor = DefaultFlavor] = object
|
JsonWriter*[Flavor = DefaultFlavor] = object
|
||||||
stream*: OutputStream
|
stream*: OutputStream
|
||||||
hasTypeAnnotations: bool
|
hasTypeAnnotations: bool
|
||||||
hasPrettyOutput*: bool # read-only
|
hasPrettyOutput*: bool # read-only
|
||||||
nestingLevel*: int # read-only
|
nestingLevel*: int # read-only
|
||||||
state: JsonWriterState
|
state: JsonWriterState
|
||||||
nesting: JsonNesting
|
|
||||||
prevNesting: seq[JsonNesting]
|
|
||||||
|
|
||||||
Json.setWriter JsonWriter,
|
Json.setWriter JsonWriter,
|
||||||
PreferredOutput = string
|
PreferredOutput = string
|
||||||
|
@ -45,11 +38,7 @@ func init*(W: type JsonWriter, stream: OutputStream,
|
||||||
hasPrettyOutput: pretty,
|
hasPrettyOutput: pretty,
|
||||||
hasTypeAnnotations: typeAnnotations,
|
hasTypeAnnotations: typeAnnotations,
|
||||||
nestingLevel: if pretty: 0 else: -1,
|
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, T: type)
|
||||||
proc beginRecord*(w: var JsonWriter)
|
proc beginRecord*(w: var JsonWriter)
|
||||||
|
@ -101,8 +90,6 @@ template fieldWritten*(w: var JsonWriter) =
|
||||||
proc beginRecord*(w: var JsonWriter) =
|
proc beginRecord*(w: var JsonWriter) =
|
||||||
doAssert w.state == RecordExpected
|
doAssert w.state == RecordExpected
|
||||||
|
|
||||||
w.prevNesting.add w.nesting
|
|
||||||
w.nesting = JsonNesting.WriteObject
|
|
||||||
append '{'
|
append '{'
|
||||||
if w.hasPrettyOutput:
|
if w.hasPrettyOutput:
|
||||||
w.nestingLevel += 2
|
w.nestingLevel += 2
|
||||||
|
@ -122,15 +109,12 @@ proc endRecord*(w: var JsonWriter) =
|
||||||
indent()
|
indent()
|
||||||
|
|
||||||
append '}'
|
append '}'
|
||||||
w.nesting = w.prevNesting.pop()
|
|
||||||
|
|
||||||
template endRecordField*(w: var JsonWriter) =
|
template endRecordField*(w: var JsonWriter) =
|
||||||
endRecord(w)
|
endRecord(w)
|
||||||
w.state = AfterField
|
w.state = AfterField
|
||||||
|
|
||||||
iterator stepwiseArrayCreation*[C](w: var JsonWriter, collection: C): auto =
|
iterator stepwiseArrayCreation*[C](w: var JsonWriter, collection: C): auto =
|
||||||
w.prevNesting.add w.nesting
|
|
||||||
w.nesting = JsonNesting.WriteArray
|
|
||||||
append '['
|
append '['
|
||||||
|
|
||||||
if w.hasPrettyOutput:
|
if w.hasPrettyOutput:
|
||||||
|
@ -156,7 +140,6 @@ iterator stepwiseArrayCreation*[C](w: var JsonWriter, collection: C): auto =
|
||||||
indent()
|
indent()
|
||||||
|
|
||||||
append ']'
|
append ']'
|
||||||
w.nesting = w.prevNesting.pop()
|
|
||||||
|
|
||||||
proc writeIterable*(w: var JsonWriter, collection: auto) =
|
proc writeIterable*(w: var JsonWriter, collection: auto) =
|
||||||
mixin writeValue
|
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[N](v: array[N, char]): bool = true
|
||||||
template isStringLike(v: auto): bool = false
|
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,
|
template writeObjectField*[FieldType, RecordType](w: var JsonWriter,
|
||||||
record: RecordType,
|
record: RecordType,
|
||||||
fieldName: static string,
|
fieldName: static string,
|
||||||
field: FieldType): bool =
|
field: FieldType) =
|
||||||
mixin writeFieldIMPL, writeValue
|
mixin writeFieldIMPL, writeValue
|
||||||
|
|
||||||
w.writeFieldName(fieldName)
|
w.writeFieldName(fieldName)
|
||||||
|
@ -185,17 +172,26 @@ template writeObjectField*[FieldType, RecordType](w: var JsonWriter,
|
||||||
else:
|
else:
|
||||||
type R = type record
|
type R = type record
|
||||||
w.writeFieldIMPL(FieldTag[R, fieldName], field, record)
|
w.writeFieldIMPL(FieldTag[R, fieldName], field, record)
|
||||||
true
|
|
||||||
|
|
||||||
proc writeRecordValue*(w: var JsonWriter, value: auto)
|
proc writeRecordValue*(w: var JsonWriter, value: auto)
|
||||||
{.gcsafe, raises: [IOError].} =
|
{.gcsafe, raises: [IOError].} =
|
||||||
mixin enumInstanceSerializedFields, writeObjectField
|
mixin enumInstanceSerializedFields, writeObjectField
|
||||||
|
mixin flavorOmitsOptionalFields, shouldWriteObjectField
|
||||||
|
|
||||||
|
type
|
||||||
|
Writer = typeof w
|
||||||
|
Flavor = Writer.Flavor
|
||||||
|
|
||||||
type RecordType = type value
|
type RecordType = type value
|
||||||
w.beginRecord RecordType
|
w.beginRecord RecordType
|
||||||
value.enumInstanceSerializedFields(fieldName, fieldType):
|
value.enumInstanceSerializedFields(fieldName, fieldValue):
|
||||||
when fieldType isnot JsonVoid:
|
when fieldValue isnot JsonVoid:
|
||||||
if writeObjectField(w, value, fieldName, fieldType):
|
when flavorOmitsOptionalFields(Flavor):
|
||||||
|
if shouldWriteObjectField(fieldValue):
|
||||||
|
writeObjectField(w, value, fieldName, fieldValue)
|
||||||
|
w.state = AfterField
|
||||||
|
else:
|
||||||
|
writeObjectField(w, value, fieldName, fieldValue)
|
||||||
w.state = AfterField
|
w.state = AfterField
|
||||||
else:
|
else:
|
||||||
discard fieldName
|
discard fieldName
|
||||||
|
|
|
@ -988,7 +988,7 @@ suite "Custom parser tests":
|
||||||
check dData.name == "FancyUInt"
|
check dData.name == "FancyUInt"
|
||||||
check dData.data.uint == 12345u
|
check dData.data.uint == 12345u
|
||||||
check customVisit.entry == JsonValueKind.String
|
check customVisit.entry == JsonValueKind.String
|
||||||
|
|
||||||
test "Parser on text blob with embedded quote (backlash escape support)":
|
test "Parser on text blob with embedded quote (backlash escape support)":
|
||||||
customVisit = TokenRegistry.default
|
customVisit = TokenRegistry.default
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,21 @@ import
|
||||||
../json_serialization/std/options,
|
../json_serialization/std/options,
|
||||||
../json_serialization
|
../json_serialization
|
||||||
|
|
||||||
|
type
|
||||||
|
ObjectWithOptionalFields = object
|
||||||
|
a: Opt[int]
|
||||||
|
b: Option[string]
|
||||||
|
c: int
|
||||||
|
|
||||||
createJsonFlavor YourJson,
|
createJsonFlavor YourJson,
|
||||||
omitOptionalFields = false
|
omitOptionalFields = false
|
||||||
|
|
||||||
createJsonFlavor MyJson,
|
createJsonFlavor MyJson,
|
||||||
omitOptionalFields = true
|
omitOptionalFields = true
|
||||||
|
|
||||||
|
ObjectWithOptionalFields.useDefaultSerializationIn YourJson
|
||||||
|
ObjectWithOptionalFields.useDefaultSerializationIn MyJson
|
||||||
|
|
||||||
suite "Test writer":
|
suite "Test writer":
|
||||||
test "stdlib option top level some YourJson":
|
test "stdlib option top level some YourJson":
|
||||||
var val = some(123)
|
var val = some(123)
|
||||||
|
@ -99,3 +108,28 @@ suite "Test writer":
|
||||||
var val = [Opt.some(123), Opt.none(int), Opt.some(777)]
|
var val = [Opt.some(123), Opt.none(int), Opt.some(777)]
|
||||||
let json = MyJson.encode(val)
|
let json = MyJson.encode(val)
|
||||||
check json == "[123,null,777]"
|
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