From a883424d0d7e5212040ef30df5fa00818e1a2c0e Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 9 Apr 2017 11:49:50 +0200 Subject: [PATCH] Implements else branch for JSON unmarshalling of object variants. --- lib/pure/json.nim | 87 +++++++++++++++++++++++++++++-------- tests/stdlib/tjsonmacro.nim | 38 +++++++++++++++- 2 files changed, 106 insertions(+), 19 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 752501465f..356f15fa53 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -1303,8 +1303,14 @@ template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind], raise newException(JsonKindError, msg) proc getEnum(node: JsonNode, ast: string, T: typedesc): T = - verifyJsonKind(node, {JString}, ast) - return parseEnum[T](node.getStr()) + when T is SomeInteger: + # TODO: I shouldn't need this proc. + proc convert[T](x: BiggestInt): T = T(x) + verifyJsonKind(node, {JInt}, ast) + return convert[T](node.getNum()) + else: + verifyJsonKind(node, {JString}, ast) + return parseEnum[T](node.getStr()) proc toIdentNode(typeNode: NimNode): NimNode = ## Converts a Sym type node (returned by getType et al.) into an @@ -1322,26 +1328,32 @@ proc toIdentNode(typeNode: NimNode): NimNode = else: assert false, "Cannot convert typeNode to an ident node: " & $typeNode.kind -proc createIfStmtForOf(ofBranch, jsonNode, kindType, - value: NimNode): NimNode {.compileTime.} = - ## Transforms a case of branch into an if statement to be placed as the - ## ExprColonExpr body expr. - expectKind(ofBranch, nnkOfBranch) - +proc createGetEnumCall(jsonNode, kindType: NimNode): NimNode = # -> getEnum(`jsonNode`, `kindType`) let getEnumSym = bindSym("getEnum") let astStrLit = toStrLit(jsonNode) let getEnumCall = newCall(getEnumSym, jsonNode, astStrLit, kindType) + return getEnumCall - var cond = newEmptyNode() +proc createOfBranchCond(ofBranch, getEnumCall: NimNode): NimNode = + var cond = newIdentNode("false") for ofCond in ofBranch: if ofCond.kind == nnkRecList: break - if cond.kind == nnkEmpty: - cond = infix(getEnumCall, "==", ofCond) - else: - cond = infix(cond, "or", infix(getEnumCall, "==", ofCond)) + let comparison = infix(getEnumCall, "==", ofCond) + cond = infix(cond, "or", comparison) + + return cond + +proc createIfStmtForOf(ofBranch, jsonNode, kindType, + value: NimNode): NimNode {.compileTime.} = + ## Transforms a case ``of`` branch into an if statement to be placed as the + ## ExprColonExpr body expr. + expectKind(ofBranch, nnkOfBranch) + + let getEnumCall = createGetEnumCall(jsonNode, kindType) + let cond = createOfBranchCond(ofBranch, getEnumCall) return newIfStmt( (cond, value) @@ -1378,6 +1390,43 @@ proc processOfBranch(ofBranch, jsonNode, kindType, let ifStmt = createIfStmtForOf(ofBranch, kindJsonNode, kindType, objField[1]) exprColonExpr.add(ifStmt) +proc processElseBranch(recCaseNode, elseBranch, jsonNode, kindType, + kindJsonNode: NimNode): seq[NimNode] {.compileTime.} = + ## Processes each field inside of a variant object's ``else`` branch. + ## + ## ..code-block::plain + ## Else + ## RecList + ## Sym "other" + result = @[] + # TODO: Remove duplication between processOfBranch + let getEnumCall = createGetEnumCall(kindJsonNode, kindType) + + # We need to build up a list of conditions from each ``of`` branch so that + # we can then negate it to get ``else``. + var cond = newIdentNode("false") + for i in 1 .. var res = Object() - var resIdent = newIdentNode("res") + var resIdent = genSym(nskVar, "res") # TODO: Placing `node[0]` inside quote is buggy var resType = toIdentNode(node[0]) diff --git a/tests/stdlib/tjsonmacro.nim b/tests/stdlib/tjsonmacro.nim index f0f0e6b567..b5d73240eb 100644 --- a/tests/stdlib/tjsonmacro.nim +++ b/tests/stdlib/tjsonmacro.nim @@ -55,8 +55,42 @@ when isMainModule: doAssert y.test3 doAssert y.testNil.isNil - # TODO: Test for custom object variants (without an enum). - # TODO: Test for object variant with an else branch. + # Test for custom object variants (without an enum) and with an else branch. + block: + type + TestVariant = object + name: string + case age: uint8 + of 2: + preSchool: string + of 8: + primarySchool: string + else: + other: int + + var node = %{ + "name": %"Nim", + "age": %8, + "primarySchool": %"Sandtown" + } + + var result = to(node, TestVariant) + doAssert result.age == 8 + doAssert result.name == "Nim" + doAssert result.primarySchool == "Sandtown" + + node = %{ + "name": %"⚔️Foo☢️", + "age": %25, + "other": %98 + } + + result = to(node, TestVariant) + doAssert result.name == node["name"].getStr() + doAssert result.age == node["age"].getNum().uint8 + doAssert result.other == node["other"].getNum() + + # TODO: Test object variant with set in of branch. # Tests that verify the error messages for invalid data. block: