mirror of
https://github.com/status-im/nim-json-serialization.git
synced 2025-02-16 12:07:35 +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: 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
|
||||
# openArray[char] - we treat any array-like thing-of-characters as a string in
|
||||
# the output
|
||||
@ -564,44 +613,54 @@ proc readValue*[T](r: var JsonReader, value: var T)
|
||||
type T = type(value)
|
||||
r.skipToken tkCurlyLe
|
||||
|
||||
const expectedFields = T.totalSerializedFields
|
||||
var readFields = 0
|
||||
when expectedFields > 0:
|
||||
let fields = T.fieldReadersTable(ReaderType)
|
||||
var expectedFieldPos = 0
|
||||
when T.totalSerializedFields > 0:
|
||||
let
|
||||
fieldsTable = T.fieldReadersTable(ReaderType)
|
||||
|
||||
const
|
||||
expectedFields = T.expectedFieldsBitmask
|
||||
|
||||
var
|
||||
encounteredFields: typeof(expectedFields)
|
||||
mostLikelyNextField = 0
|
||||
|
||||
while true:
|
||||
# Have the assignment parsed of the AVP
|
||||
if r.lexer.lazyTok == tkQuoted:
|
||||
r.lexer.accept
|
||||
if r.lexer.lazyTok != tkString:
|
||||
break
|
||||
# Calculate/assemble handler
|
||||
|
||||
when T is tuple:
|
||||
var reader = fields[][expectedFieldPos].reader
|
||||
expectedFieldPos += 1
|
||||
let fieldIdx = mostLikelyNextField
|
||||
mostLikelyNextField += 1
|
||||
else:
|
||||
var reader = findFieldReader(fields[], r.lexer.strVal, expectedFieldPos)
|
||||
if reader != nil:
|
||||
let fieldIdx = findFieldIdx(fieldsTable[],
|
||||
r.lexer.strVal,
|
||||
mostLikelyNextField)
|
||||
if fieldIdx != -1:
|
||||
let reader = fieldsTable[][fieldIdx].reader
|
||||
r.lexer.next()
|
||||
r.skipToken tkColon
|
||||
reader(value, r)
|
||||
inc readFields
|
||||
else:
|
||||
encounteredFields.setBit(fieldIdx)
|
||||
elif r.allowUnknownFields:
|
||||
r.lexer.next()
|
||||
r.skipToken tkColon
|
||||
if r.allowUnknownFields:
|
||||
r.skipSingleJsValue()
|
||||
else:
|
||||
const typeName = typetraits.name(T)
|
||||
r.raiseUnexpectedField(r.lexer.strVal, cstring typeName)
|
||||
r.skipSingleJsValue()
|
||||
else:
|
||||
const typeName = typetraits.name(T)
|
||||
r.raiseUnexpectedField(r.lexer.strVal, cstring typeName)
|
||||
|
||||
if r.lexer.lazyTok == tkComma:
|
||||
r.lexer.next()
|
||||
else:
|
||||
break
|
||||
|
||||
if r.requireAllFields and readFields != expectedFields:
|
||||
const typeName = typetraits.name(T)
|
||||
r.raiseIncompleteObject(typeName)
|
||||
if r.requireAllFields and
|
||||
not expectedFields.isBitwiseSubsetOf(encounteredFields):
|
||||
const typeName = typetraits.name(T)
|
||||
r.raiseIncompleteObject(typeName)
|
||||
|
||||
r.lexer.accept
|
||||
r.skipToken tkCurlyRi
|
||||
|
@ -30,3 +30,6 @@ proc readValue*[T](reader: var JsonReader, value: var Result[T, void]) =
|
||||
reader.lexer.next()
|
||||
else:
|
||||
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)
|
||||
|
||||
test "Option types":
|
||||
check:
|
||||
2 == static(HoldsOption.totalSerializedFields)
|
||||
1 == static(HoldsOption.totalExpectedFields)
|
||||
|
||||
2 == static(Foo.totalSerializedFields)
|
||||
2 == static(Foo.totalExpectedFields)
|
||||
|
||||
let
|
||||
h1 = HoldsOption(o: some Simple(x: 1, y: "2", distance: 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 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":
|
||||
check:
|
||||
false == static(isFieldExpected Opt[Simple])
|
||||
2 == static(HoldsResultOpt.totalSerializedFields)
|
||||
1 == static(HoldsResultOpt.totalExpectedFields)
|
||||
|
||||
let
|
||||
h1 = HoldsResultOpt(o: Opt[Simple].ok Simple(x: 1, y: "2", distance: 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 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":
|
||||
let obj = WithCustomFieldRule(str: "test", intVal: 10)
|
||||
Json.roundtripTest obj, """{"str":"test","intVal":"10"}"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user