Add JsonReader.init(..., requireAllFields: bool)
This commit is contained in:
parent
652099a959
commit
4f3775ddf4
|
@ -14,6 +14,7 @@ type
|
||||||
JsonReader*[Flavor = DefaultFlavor] = object
|
JsonReader*[Flavor = DefaultFlavor] = object
|
||||||
lexer*: JsonLexer
|
lexer*: JsonLexer
|
||||||
allowUnknownFields: bool
|
allowUnknownFields: bool
|
||||||
|
requireAllFields: bool
|
||||||
|
|
||||||
JsonReaderError* = object of JsonError
|
JsonReaderError* = object of JsonError
|
||||||
line*, col*: int
|
line*, col*: int
|
||||||
|
@ -46,6 +47,9 @@ type
|
||||||
|
|
||||||
UnexpectedValueError* = object of JsonReaderError
|
UnexpectedValueError* = object of JsonReaderError
|
||||||
|
|
||||||
|
IncompleteObjectError* = object of JsonReaderError
|
||||||
|
objectType: cstring
|
||||||
|
|
||||||
IntOverflowError* = object of JsonReaderError
|
IntOverflowError* = object of JsonReaderError
|
||||||
isNegative: bool
|
isNegative: bool
|
||||||
absIntVal: uint64
|
absIntVal: uint64
|
||||||
|
@ -79,6 +83,9 @@ method formatMsg*(err: ref IntOverflowError, filename: string): string =
|
||||||
method formatMsg*(err: ref UnexpectedValueError, filename: string): string =
|
method formatMsg*(err: ref UnexpectedValueError, filename: string): string =
|
||||||
tryFmt: fmt"{filename}({err.line}, {err.col}) {err.msg}"
|
tryFmt: fmt"{filename}({err.line}, {err.col}) {err.msg}"
|
||||||
|
|
||||||
|
method formatMsg*(err: ref IncompleteObjectError, filename: string): string =
|
||||||
|
tryFmt: fmt"{filename}({err.line}, {err.col}) Not all required fields were specified when reading '{err.objectType}'"
|
||||||
|
|
||||||
proc assignLineNumber*(ex: ref JsonReaderError, r: JsonReader) =
|
proc assignLineNumber*(ex: ref JsonReaderError, r: JsonReader) =
|
||||||
ex.line = r.lexer.line
|
ex.line = r.lexer.line
|
||||||
ex.col = r.lexer.tokenStartCol
|
ex.col = r.lexer.tokenStartCol
|
||||||
|
@ -111,6 +118,12 @@ proc raiseUnexpectedField*(r: JsonReader, fieldName, deserializedType: cstring)
|
||||||
ex.deserializedType = deserializedType
|
ex.deserializedType = deserializedType
|
||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
|
proc raiseIncompleteObject*(r: JsonReader, objectType: cstring) {.noreturn.} =
|
||||||
|
var ex = new IncompleteObjectError
|
||||||
|
ex.assignLineNumber(r)
|
||||||
|
ex.objectType = objectType
|
||||||
|
raise ex
|
||||||
|
|
||||||
proc handleReadException*(r: JsonReader,
|
proc handleReadException*(r: JsonReader,
|
||||||
Record: type,
|
Record: type,
|
||||||
fieldName: string,
|
fieldName: string,
|
||||||
|
@ -125,8 +138,10 @@ proc handleReadException*(r: JsonReader,
|
||||||
proc init*(T: type JsonReader,
|
proc init*(T: type JsonReader,
|
||||||
stream: InputStream,
|
stream: InputStream,
|
||||||
mode = defaultJsonMode,
|
mode = defaultJsonMode,
|
||||||
allowUnknownFields = false): T =
|
allowUnknownFields = false,
|
||||||
|
requireAllFields = false): T =
|
||||||
result.allowUnknownFields = allowUnknownFields
|
result.allowUnknownFields = allowUnknownFields
|
||||||
|
result.requireAllFields = requireAllFields
|
||||||
result.lexer = JsonLexer.init(stream, mode)
|
result.lexer = JsonLexer.init(stream, mode)
|
||||||
result.lexer.next()
|
result.lexer.next()
|
||||||
|
|
||||||
|
@ -500,7 +515,9 @@ proc readValue*[T](r: var JsonReader, value: var T)
|
||||||
type T = type(value)
|
type T = type(value)
|
||||||
r.skipToken tkCurlyLe
|
r.skipToken tkCurlyLe
|
||||||
|
|
||||||
when T.totalSerializedFields > 0:
|
const expectedFields = T.totalSerializedFields
|
||||||
|
var readFields = 0
|
||||||
|
when expectedFields > 0:
|
||||||
let fields = T.fieldReadersTable(ReaderType)
|
let fields = T.fieldReadersTable(ReaderType)
|
||||||
var expectedFieldPos = 0
|
var expectedFieldPos = 0
|
||||||
while r.lexer.tok == tkString:
|
while r.lexer.tok == tkString:
|
||||||
|
@ -513,6 +530,7 @@ proc readValue*[T](r: var JsonReader, value: var T)
|
||||||
r.skipToken tkColon
|
r.skipToken tkColon
|
||||||
if reader != nil:
|
if reader != nil:
|
||||||
reader(value, r)
|
reader(value, r)
|
||||||
|
inc readFields
|
||||||
elif r.allowUnknownFields:
|
elif r.allowUnknownFields:
|
||||||
r.skipSingleJsValue()
|
r.skipSingleJsValue()
|
||||||
else:
|
else:
|
||||||
|
@ -523,6 +541,10 @@ proc readValue*[T](r: var JsonReader, value: var T)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if r.requireAllFields and readFields != expectedFields:
|
||||||
|
const typeName = typetraits.name(T)
|
||||||
|
r.raiseIncompleteObject(typeName)
|
||||||
|
|
||||||
r.skipToken tkCurlyRi
|
r.skipToken tkCurlyRi
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -132,6 +132,85 @@ suite "toJson tests":
|
||||||
let shouldNotDecode = Json.decode(json, Simple)
|
let shouldNotDecode = Json.decode(json, Simple)
|
||||||
echo "This should not have decoded ", shouldNotDecode
|
echo "This should not have decoded ", shouldNotDecode
|
||||||
|
|
||||||
|
test "all fields are required and present":
|
||||||
|
let json = dedent"""
|
||||||
|
{
|
||||||
|
"x": 20,
|
||||||
|
"distance": 10,
|
||||||
|
"y": "y value"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let decoded = Json.decode(json, Simple, requireAllFields = true)
|
||||||
|
|
||||||
|
check:
|
||||||
|
decoded.x == 20
|
||||||
|
decoded.y == "y value"
|
||||||
|
decoded.distance.int == 10
|
||||||
|
|
||||||
|
test "all fields were required, but not all were provided":
|
||||||
|
let json = dedent"""
|
||||||
|
{
|
||||||
|
"x": -20,
|
||||||
|
"distance": 10
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
expect IncompleteObjectError:
|
||||||
|
let shouldNotDecode = Json.decode(json, Simple, requireAllFields = true)
|
||||||
|
echo "This should not have decoded ", shouldNotDecode
|
||||||
|
|
||||||
|
test "all fields were required, but not all were provided (additional fields present instead)":
|
||||||
|
let json = dedent"""
|
||||||
|
{
|
||||||
|
"futureBool": false,
|
||||||
|
"y": "y value",
|
||||||
|
"futureObject": {"a": -1, "b": [1, 2.0, 3.1], "c": null, "d": true},
|
||||||
|
"distance": 10
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
expect IncompleteObjectError:
|
||||||
|
let shouldNotDecode = Json.decode(json, Simple,
|
||||||
|
requireAllFields = true,
|
||||||
|
allowUnknownFields = true)
|
||||||
|
echo "This should not have decoded ", shouldNotDecode
|
||||||
|
|
||||||
|
test "all fields were required, but none were provided":
|
||||||
|
let json = "{}"
|
||||||
|
|
||||||
|
expect IncompleteObjectError:
|
||||||
|
let shouldNotDecode = Json.decode(json, Simple, requireAllFields = true)
|
||||||
|
echo "This should not have decoded ", shouldNotDecode
|
||||||
|
|
||||||
|
test "all fields are required and provided, and additional ones are present":
|
||||||
|
let json = dedent"""
|
||||||
|
{
|
||||||
|
"x": 20,
|
||||||
|
"distance": 10,
|
||||||
|
"futureBool": false,
|
||||||
|
"y": "y value",
|
||||||
|
"futureObject": {"a": -1, "b": [1, 2.0, 3.1], "c": null, "d": true},
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
let decoded = try:
|
||||||
|
Json.decode(json, Simple, requireAllFields = true, allowUnknownFields = true)
|
||||||
|
except SerializationError as err:
|
||||||
|
checkpoint "Unexpected deserialization failure: " & err.formatMsg("<input>")
|
||||||
|
raise
|
||||||
|
|
||||||
|
check:
|
||||||
|
decoded.x == 20
|
||||||
|
decoded.y == "y value"
|
||||||
|
decoded.distance.int == 10
|
||||||
|
|
||||||
|
expect UnexpectedField:
|
||||||
|
let shouldNotDecode = Json.decode(json, Simple,
|
||||||
|
requireAllFields = true,
|
||||||
|
allowUnknownFields = false)
|
||||||
|
echo "This should not have decoded ", shouldNotDecode
|
||||||
|
|
||||||
test "arrays are printed correctly":
|
test "arrays are printed correctly":
|
||||||
var x = HoldsArray(data: @[1, 2, 3, 4])
|
var x = HoldsArray(data: @[1, 2, 3, 4])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue