bugfix: a leading field with a 'none' value was producing an incorrect encoding (#50)

The field was omitted, but not the comma following it, resulting in an
encoding such as '{, otherFields: ...}'
This commit is contained in:
Tanguy 2022-07-15 12:23:35 +02:00 committed by GitHub
parent 97cf184119
commit e5b18fb710
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 12 deletions

View File

@ -418,7 +418,7 @@ func expectedFieldsBitmask*(TT: type): auto {.compileTime.} =
enumAllSerializedFields enumAllSerializedFields
const requiredWords = const requiredWords =
(totalExpectedFields(T) + bitsPerWord - 1) div bitsPerWord (totalSerializedFields(T) + bitsPerWord - 1) div bitsPerWord
var res: array[requiredWords, uint] var res: array[requiredWords, uint]

View File

@ -4,11 +4,13 @@ export options
template writeObjectField*(w: var JsonWriter, template writeObjectField*(w: var JsonWriter,
record: auto, record: auto,
fieldName: static string, fieldName: static string,
field: Option) = field: Option): bool =
mixin writeObjectField mixin writeObjectField
if field.isSome: if field.isSome:
writeObjectField(w, record, fieldName, field.get) writeObjectField(w, record, fieldName, field.get)
else:
false
proc writeValue*(writer: var JsonWriter, value: Option) = proc writeValue*(writer: var JsonWriter, value: Option) =
mixin writeValue mixin writeValue

View File

@ -7,11 +7,13 @@ export
template writeObjectField*[T](w: var JsonWriter, template writeObjectField*[T](w: var JsonWriter,
record: auto, record: auto,
fieldName: static string, fieldName: static string,
field: Result[T, void]) = field: Result[T, void]): bool =
mixin writeObjectField mixin writeObjectField
if field.isOk: if field.isOk:
writeObjectField(w, record, fieldName, field.get) writeObjectField(w, record, fieldName, field.get)
else:
false
proc writeValue*[T](writer: var JsonWriter, value: Result[T, void]) = proc writeValue*[T](writer: var JsonWriter, value: Result[T, void]) =
mixin writeValue mixin writeValue

View File

@ -148,7 +148,7 @@ template isStringLike(v: auto): bool = false
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) = field: FieldType): bool =
mixin writeFieldIMPL, writeValue mixin writeFieldIMPL, writeValue
type type
@ -159,6 +159,7 @@ template writeObjectField*[FieldType, RecordType](w: var JsonWriter,
w.writeValue(field) w.writeValue(field)
else: else:
w.writeFieldIMPL(FieldTag[R, fieldName], field, record) w.writeFieldIMPL(FieldTag[R, fieldName], field, record)
true
proc writeValue*(w: var JsonWriter, value: auto) = proc writeValue*(w: var JsonWriter, value: auto) =
mixin enumInstanceSerializedFields, writeValue mixin enumInstanceSerializedFields, writeValue
@ -238,8 +239,8 @@ proc writeValue*(w: var JsonWriter, value: auto) =
w.beginRecord RecordType w.beginRecord RecordType
value.enumInstanceSerializedFields(fieldName, field): value.enumInstanceSerializedFields(fieldName, field):
mixin writeObjectField mixin writeObjectField
writeObjectField(w, value, fieldName, field) if writeObjectField(w, value, fieldName, field):
w.state = AfterField w.state = AfterField
w.endRecord() w.endRecord()
else: else:

View File

@ -74,13 +74,30 @@ type
dup: bool dup: bool
HoldsResultOpt* = object HoldsResultOpt* = object
r*: ref Simple
o*: Opt[Simple] o*: Opt[Simple]
r*: ref Simple
WithCustomFieldRule* = object WithCustomFieldRule* = object
str*: string str*: string
intVal*: int intVal*: int
OtherOptionTest* = object
a*: Option[Meter]
b*: Option[Meter]
NestedOptionTest* = object
c*: Option[OtherOptionTest]
d*: Option[OtherOptionTest]
SeqOptionTest* = object
a*: seq[Option[Meter]]
b*: Meter
OtherOptionTest2* = object
a*: Option[Meter]
b*: Option[Meter]
c*: Option[Meter]
var var
customVisit: TokenRegistry customVisit: TokenRegistry
@ -350,20 +367,70 @@ suite "toJson tests":
let let
h1 = HoldsOption(o: some Simple(x: 1, y: "2", distance: Meter(3))) h1 = HoldsOption(o: some Simple(x: 1, y: "2", distance: Meter(3)))
h2 = HoldsOption(r: newSimple(1, "2", Meter(3))) h2 = HoldsOption(r: newSimple(1, "2", Meter(3)))
h3 = Json.decode("""{"r":{"distance":3,"x":1,"y":"2"}}""",
HoldsOption, requireAllFields = true)
Json.roundtripTest h1, """{"r":null,"o":{"distance":3,"x":1,"y":"2"}}""" Json.roundtripTest h1, """{"r":null,"o":{"distance":3,"x":1,"y":"2"}}"""
Json.roundtripTest h2, """{"r":{"distance":3,"x":1,"y":"2"}}""" Json.roundtripTest h2, """{"r":{"distance":3,"x":1,"y":"2"}}"""
let
h3 = Json.decode("""{"r":{"distance":3,"x":1,"y":"2"}}""",
HoldsOption, requireAllFields = true)
check h3 == h2 check h3 == h2
expect SerializationError: expect SerializationError:
let h4 = Json.decode("""{"o":{"distance":3,"x":1,"y":"2"}}""", let h4 = Json.decode("""{"o":{"distance":3,"x":1,"y":"2"}}""",
HoldsOption, requireAllFields = true) HoldsOption, requireAllFields = true)
test "Nested option types":
let
h3 = OtherOptionTest()
h4 = OtherOptionTest(a: some Meter(1))
h5 = OtherOptionTest(b: some Meter(2))
h6 = OtherOptionTest(a: some Meter(3), b: some Meter(4))
Json.roundtripTest h3, """{}"""
Json.roundtripTest h4, """{"a":1}"""
Json.roundtripTest h5, """{"b":2}"""
Json.roundtripTest h6, """{"a":3,"b":4}"""
let
arr = @[some h3, some h4, some h5, some h6, none(OtherOptionTest)]
results = @[
"""{"c":{},"d":{}}""",
"""{"c":{},"d":{"a":1}}""",
"""{"c":{},"d":{"b":2}}""",
"""{"c":{},"d":{"a":3,"b":4}}""",
"""{"c":{}}""",
"""{"c":{"a":1},"d":{}}""",
"""{"c":{"a":1},"d":{"a":1}}""",
"""{"c":{"a":1},"d":{"b":2}}""",
"""{"c":{"a":1},"d":{"a":3,"b":4}}""",
"""{"c":{"a":1}}""",
"""{"c":{"b":2},"d":{}}""",
"""{"c":{"b":2},"d":{"a":1}}""",
"""{"c":{"b":2},"d":{"b":2}}""",
"""{"c":{"b":2},"d":{"a":3,"b":4}}""",
"""{"c":{"b":2}}""",
"""{"c":{"a":3,"b":4},"d":{}}""",
"""{"c":{"a":3,"b":4},"d":{"a":1}}""",
"""{"c":{"a":3,"b":4},"d":{"b":2}}""",
"""{"c":{"a":3,"b":4},"d":{"a":3,"b":4}}""",
"""{"c":{"a":3,"b":4}}""",
"""{"d":{}}""",
"""{"d":{"a":1}}""",
"""{"d":{"b":2}}""",
"""{"d":{"a":3,"b":4}}""",
"""{}"""
]
var r = 0
for a in arr:
for b in arr:
Json.roundtripTest NestedOptionTest(c: a, d: b), results[r]
r.inc
Json.roundtripTest SeqOptionTest(a: @[some 5.Meter, none Meter], b: Meter(5)), """{"a":[5,null],"b":5}"""
Json.roundtripTest OtherOptionTest2(a: some 5.Meter, b: none Meter, c: some 10.Meter), """{"a":5,"c":10}"""
test "Result Opt types": test "Result Opt types":
check: check:
false == static(isFieldExpected Opt[Simple]) false == static(isFieldExpected Opt[Simple])
@ -374,7 +441,7 @@ suite "toJson tests":
h1 = HoldsResultOpt(o: Opt[Simple].ok Simple(x: 1, y: "2", distance: Meter(3))) h1 = HoldsResultOpt(o: Opt[Simple].ok Simple(x: 1, y: "2", distance: Meter(3)))
h2 = HoldsResultOpt(r: newSimple(1, "2", Meter(3))) h2 = HoldsResultOpt(r: newSimple(1, "2", Meter(3)))
Json.roundtripTest h1, """{"r":null,"o":{"distance":3,"x":1,"y":"2"}}""" Json.roundtripTest h1, """{"o":{"distance":3,"x":1,"y":"2"},"r":null}"""
Json.roundtripTest h2, """{"r":{"distance":3,"x":1,"y":"2"}}""" Json.roundtripTest h2, """{"r":{"distance":3,"x":1,"y":"2"}}"""
let let