Fixes null handling for object refs in JSON unmarshal macro.

This commit is contained in:
Dominik Picheta 2017-04-17 18:43:31 +02:00
commit 793d6efffb
2 changed files with 33 additions and 9 deletions

View file

@ -1502,7 +1502,7 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] =
doAssert result.len > 0 doAssert result.len > 0
proc processType(typeName: NimNode, obj: NimNode, proc processType(typeName: NimNode, obj: NimNode,
jsonNode: NimNode): NimNode {.compileTime.} = jsonNode: NimNode, isRef: bool): NimNode {.compileTime.} =
## Process a type such as ``Sym "float"`` or ``ObjectTy ...``. ## Process a type such as ``Sym "float"`` or ``ObjectTy ...``.
## ##
## Sample ``ObjectTy``: ## Sample ``ObjectTy``:
@ -1524,6 +1524,20 @@ proc processType(typeName: NimNode, obj: NimNode,
for field in obj[2]: for field in obj[2]:
let nodes = processObjField(field, jsonNode) let nodes = processObjField(field, jsonNode)
result.add(nodes) result.add(nodes)
# Object might be null. So we need to check for that.
if isRef:
result = quote do:
verifyJsonKind(`jsonNode`, {JObject, JNull}, astToStr(`jsonNode`))
if `jsonNode`.kind == JNull:
nil
else:
`result`
else:
result = quote do:
verifyJsonKind(`jsonNode`, {JObject}, astToStr(`jsonNode`));
`result`
of nnkEnumTy: of nnkEnumTy:
let instType = toIdentNode(getTypeInst(typeName)) let instType = toIdentNode(getTypeInst(typeName))
let getEnumCall = createGetEnumCall(jsonNode, instType) let getEnumCall = createGetEnumCall(jsonNode, instType)
@ -1591,7 +1605,7 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode =
typeName = typeName[0 .. ^12] typeName = typeName[0 .. ^12]
let obj = getType(typeSym[1]) let obj = getType(typeSym[1])
result = processType(newIdentNode(typeName), obj, jsonNode) result = processType(newIdentNode(typeName), obj, jsonNode, true)
of "seq": of "seq":
let seqT = typeSym[1] let seqT = typeSym[1]
let forLoopI = newIdentNode("i") let forLoopI = newIdentNode("i")
@ -1611,21 +1625,21 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode =
else: else:
# Generic type. # Generic type.
let obj = getType(typeSym) let obj = getType(typeSym)
result = processType(typeSym, obj, jsonNode) result = processType(typeSym, obj, jsonNode, false)
of nnkSym: of nnkSym:
let obj = getType(typeSym) let obj = getType(typeSym)
if obj.kind == nnkBracketExpr: if obj.kind == nnkBracketExpr:
# When `Sym "Foo"` turns out to be a `ref object`. # When `Sym "Foo"` turns out to be a `ref object`.
result = createConstructor(obj, jsonNode) result = createConstructor(obj, jsonNode)
else: else:
result = processType(typeSym, obj, jsonNode) result = processType(typeSym, obj, jsonNode, false)
else: else:
doAssert false, "Unable to create constructor for: " & $typeSym.kind doAssert false, "Unable to create constructor for: " & $typeSym.kind
doAssert(not result.isNil(), "Constructor not initialised.") doAssert(not result.isNil(), "Constructor not initialised.")
proc postProcess(node: NimNode): NimNode proc postProcess(node: NimNode): NimNode
proc postProcessValue(value: NimNode, depth=0): NimNode = proc postProcessValue(value: NimNode): NimNode =
## Looks for object constructors and calls the ``postProcess`` procedure ## Looks for object constructors and calls the ``postProcess`` procedure
## on them. Otherwise it just returns the node as-is. ## on them. Otherwise it just returns the node as-is.
case value.kind case value.kind
@ -1746,9 +1760,10 @@ macro to*(node: JsonNode, T: typedesc): untyped =
doAssert(($typeNode[0]).normalize == "typedesc") doAssert(($typeNode[0]).normalize == "typedesc")
result = createConstructor(typeNode[1], node) result = createConstructor(typeNode[1], node)
result = postProcess(result) # TODO: Rename postProcessValue and move it (?)
result = postProcessValue(result)
#echo(toStrLit(result)) echo(toStrLit(result))
when false: when false:
import os import os

View file

@ -186,7 +186,7 @@ when isMainModule:
# Test ref type in field. # Test ref type in field.
block: block:
let jsonNode = parseJson(""" var jsonNode = parseJson("""
{ {
"person": { "person": {
"name": "Nimmer", "name": "Nimmer",
@ -208,4 +208,13 @@ when isMainModule:
var data = to(jsonNode, Data) var data = to(jsonNode, Data)
doAssert data.person.name == "Nimmer" doAssert data.person.name == "Nimmer"
doAssert data.person.age == 21 doAssert data.person.age == 21
doAssert data.list == @[1, 2, 3, 4] doAssert data.list == @[1, 2, 3, 4]
jsonNode = parseJson("""
{
"person": null,
"list": [1, 2, 3, 4]
}
""")
data = to(jsonNode, Data)
doAssert data.person.isNil