mirror of
https://github.com/status-im/nim-json-serialization.git
synced 2025-02-21 06:18:09 +00:00
bugfix: even the Option fields were mandatory under requireAllFields
This commit is contained in:
parent
090749e577
commit
aca66fbd8e
@ -394,6 +394,55 @@ iterator readObject*(r: var JsonReader,
|
|||||||
proc isNotNilCheck[T](x: ref T not nil) {.compileTime.} = discard
|
proc isNotNilCheck[T](x: ref T not nil) {.compileTime.} = discard
|
||||||
proc isNotNilCheck[T](x: ptr T not nil) {.compileTime.} = discard
|
proc isNotNilCheck[T](x: ptr T not nil) {.compileTime.} = discard
|
||||||
|
|
||||||
|
func isFieldExpected*(T: type): bool {.compileTime.} =
|
||||||
|
T isnot Option
|
||||||
|
|
||||||
|
func totalExpectedFields*(T: type): int {.compileTime.} =
|
||||||
|
mixin isFieldExpected,
|
||||||
|
enumAllSerializedFields
|
||||||
|
|
||||||
|
enumAllSerializedFields(T):
|
||||||
|
if isFieldExpected(FieldType):
|
||||||
|
inc result
|
||||||
|
|
||||||
|
func setBit*(x: var uint, bit: int) {.inline.} =
|
||||||
|
let mask = uint(1) shl bit
|
||||||
|
x = x or mask
|
||||||
|
|
||||||
|
const bitsPerWord = sizeof(uint) * 8
|
||||||
|
|
||||||
|
func expectedFieldsBitmask*(TT: type): auto {.compileTime.} =
|
||||||
|
type T = TT
|
||||||
|
|
||||||
|
mixin isFieldExpected,
|
||||||
|
enumAllSerializedFields
|
||||||
|
|
||||||
|
const requiredWords =
|
||||||
|
(totalExpectedFields(T) + bitsPerWord - 1) div bitsPerWord
|
||||||
|
|
||||||
|
var res: array[requiredWords, uint]
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
enumAllSerializedFields(T):
|
||||||
|
if isFieldExpected(FieldType):
|
||||||
|
res[i div bitsPerWord].setBit(i mod bitsPerWord)
|
||||||
|
inc i
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
template setBit[N](data: var array[N, uint], bitIdx: int) =
|
||||||
|
when N > 1:
|
||||||
|
data[bitIdx div bitsPerWord].setBit(bitIdx mod bitsPerWord)
|
||||||
|
else:
|
||||||
|
data[0].setBit(bitIdx)
|
||||||
|
|
||||||
|
func isBitwiseSubsetOf[N](lhs, rhs: array[N, uint]): bool =
|
||||||
|
for i in low(lhs) .. high(lhs):
|
||||||
|
if (lhs[i] and rhs[i]) != lhs[i]:
|
||||||
|
return false
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
# this construct catches `array[N, char]` which otherwise won't decompose into
|
# this construct catches `array[N, char]` which otherwise won't decompose into
|
||||||
# openArray[char] - we treat any array-like thing-of-characters as a string in
|
# openArray[char] - we treat any array-like thing-of-characters as a string in
|
||||||
# the output
|
# the output
|
||||||
@ -564,42 +613,52 @@ proc readValue*[T](r: var JsonReader, value: var T)
|
|||||||
type T = type(value)
|
type T = type(value)
|
||||||
r.skipToken tkCurlyLe
|
r.skipToken tkCurlyLe
|
||||||
|
|
||||||
const expectedFields = T.totalSerializedFields
|
when T.totalSerializedFields > 0:
|
||||||
var readFields = 0
|
let
|
||||||
when expectedFields > 0:
|
fieldsTable = T.fieldReadersTable(ReaderType)
|
||||||
let fields = T.fieldReadersTable(ReaderType)
|
|
||||||
var expectedFieldPos = 0
|
const
|
||||||
|
expectedFields = T.expectedFieldsBitmask
|
||||||
|
|
||||||
|
var
|
||||||
|
encounteredFields: typeof(expectedFields)
|
||||||
|
mostLikelyNextField = 0
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
# Have the assignment parsed of the AVP
|
# Have the assignment parsed of the AVP
|
||||||
if r.lexer.lazyTok == tkQuoted:
|
if r.lexer.lazyTok == tkQuoted:
|
||||||
r.lexer.accept
|
r.lexer.accept
|
||||||
if r.lexer.lazyTok != tkString:
|
if r.lexer.lazyTok != tkString:
|
||||||
break
|
break
|
||||||
# Calculate/assemble handler
|
|
||||||
when T is tuple:
|
when T is tuple:
|
||||||
var reader = fields[][expectedFieldPos].reader
|
let fieldIdx = mostLikelyNextField
|
||||||
expectedFieldPos += 1
|
mostLikelyNextField += 1
|
||||||
else:
|
else:
|
||||||
var reader = findFieldReader(fields[], r.lexer.strVal, expectedFieldPos)
|
let fieldIdx = findFieldIdx(fieldsTable[],
|
||||||
if reader != nil:
|
r.lexer.strVal,
|
||||||
|
mostLikelyNextField)
|
||||||
|
if fieldIdx != -1:
|
||||||
|
let reader = fieldsTable[][fieldIdx].reader
|
||||||
r.lexer.next()
|
r.lexer.next()
|
||||||
r.skipToken tkColon
|
r.skipToken tkColon
|
||||||
reader(value, r)
|
reader(value, r)
|
||||||
inc readFields
|
encounteredFields.setBit(fieldIdx)
|
||||||
else:
|
elif r.allowUnknownFields:
|
||||||
r.lexer.next()
|
r.lexer.next()
|
||||||
r.skipToken tkColon
|
r.skipToken tkColon
|
||||||
if r.allowUnknownFields:
|
|
||||||
r.skipSingleJsValue()
|
r.skipSingleJsValue()
|
||||||
else:
|
else:
|
||||||
const typeName = typetraits.name(T)
|
const typeName = typetraits.name(T)
|
||||||
r.raiseUnexpectedField(r.lexer.strVal, cstring typeName)
|
r.raiseUnexpectedField(r.lexer.strVal, cstring typeName)
|
||||||
|
|
||||||
if r.lexer.lazyTok == tkComma:
|
if r.lexer.lazyTok == tkComma:
|
||||||
r.lexer.next()
|
r.lexer.next()
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
if r.requireAllFields and readFields != expectedFields:
|
if r.requireAllFields and
|
||||||
|
not expectedFields.isBitwiseSubsetOf(encounteredFields):
|
||||||
const typeName = typetraits.name(T)
|
const typeName = typetraits.name(T)
|
||||||
r.raiseIncompleteObject(typeName)
|
r.raiseIncompleteObject(typeName)
|
||||||
|
|
||||||
|
@ -30,3 +30,6 @@ proc readValue*[T](reader: var JsonReader, value: var Result[T, void]) =
|
|||||||
reader.lexer.next()
|
reader.lexer.next()
|
||||||
else:
|
else:
|
||||||
value.ok reader.readValue(T)
|
value.ok reader.readValue(T)
|
||||||
|
|
||||||
|
func isFieldExpected*[T, E](_: type[Result[T, E]]): bool {.compileTime.} =
|
||||||
|
false
|
||||||
|
@ -340,6 +340,13 @@ suite "toJson tests":
|
|||||||
r == Json.decode("""{"type":"uint8", "renamed":"field"}""", HasUnusualFieldNames)
|
r == Json.decode("""{"type":"uint8", "renamed":"field"}""", HasUnusualFieldNames)
|
||||||
|
|
||||||
test "Option types":
|
test "Option types":
|
||||||
|
check:
|
||||||
|
2 == static(HoldsOption.totalSerializedFields)
|
||||||
|
1 == static(HoldsOption.totalExpectedFields)
|
||||||
|
|
||||||
|
2 == static(Foo.totalSerializedFields)
|
||||||
|
2 == static(Foo.totalExpectedFields)
|
||||||
|
|
||||||
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)))
|
||||||
@ -347,7 +354,22 @@ suite "toJson tests":
|
|||||||
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
|
||||||
|
|
||||||
|
expect SerializationError:
|
||||||
|
let h4 = Json.decode("""{"o":{"distance":3,"x":1,"y":"2"}}""",
|
||||||
|
HoldsOption, requireAllFields = true)
|
||||||
|
|
||||||
test "Result Opt types":
|
test "Result Opt types":
|
||||||
|
check:
|
||||||
|
false == static(isFieldExpected Opt[Simple])
|
||||||
|
2 == static(HoldsResultOpt.totalSerializedFields)
|
||||||
|
1 == static(HoldsResultOpt.totalExpectedFields)
|
||||||
|
|
||||||
let
|
let
|
||||||
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)))
|
||||||
@ -355,6 +377,16 @@ suite "toJson tests":
|
|||||||
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"}}""",
|
||||||
|
HoldsResultOpt, requireAllFields = true)
|
||||||
|
|
||||||
|
check h3 == h2
|
||||||
|
|
||||||
|
expect SerializationError:
|
||||||
|
let h4 = Json.decode("""{"o":{"distance":3,"x":1,"y":"2"}}""",
|
||||||
|
HoldsResultOpt, requireAllFields = true)
|
||||||
|
|
||||||
test "Custom field serialization":
|
test "Custom field serialization":
|
||||||
let obj = WithCustomFieldRule(str: "test", intVal: 10)
|
let obj = WithCustomFieldRule(str: "test", intVal: 10)
|
||||||
Json.roundtripTest obj, """{"str":"test","intVal":"10"}"""
|
Json.roundtripTest obj, """{"str":"test","intVal":"10"}"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user