diff options
author | Dominik Picheta <dominikpicheta@gmail.com> | 2017-04-09 11:49:50 +0200 |
---|---|---|
committer | Dominik Picheta <dominikpicheta@gmail.com> | 2017-04-09 11:49:50 +0200 |
commit | a883424d0d7e5212040ef30df5fa00818e1a2c0e (patch) | |
tree | 662433081134c305266f7535a1d6b382fd0dbd2e | |
parent | 658467a31f34110006fde3bd0ef949dd819a5601 (diff) | |
download | Nim-a883424d0d7e5212040ef30df5fa00818e1a2c0e.tar.gz |
Implements else branch for JSON unmarshalling of object variants.
-rw-r--r-- | lib/pure/json.nim | 87 | ||||
-rw-r--r-- | 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 752501465..356f15fa5 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 .. <len(recCaseNode): + if recCaseNode[i].kind == nnkElse: + break + + cond = infix(cond, "or", createOfBranchCond(recCaseNode[i], getEnumCall)) + + # Negate the condition. + cond = prefix(cond, "not") + + for branchField in elseBranch[^1]: + let objFields = processObjField(branchField, jsonNode) + + for objField in objFields: + let exprColonExpr = newNimNode(nnkExprColonExpr) + result.add(exprColonExpr) + # Add the name of the field. + exprColonExpr.add(toIdentNode(objField[0])) + + # Add the value of the field. + let ifStmt = newIfStmt((cond, objField[1])) + exprColonExpr.add(ifStmt) + proc processObjField(field, jsonNode: NimNode): seq[NimNode] = ## Process a field from a ``RecList``. ## @@ -1418,9 +1467,13 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] = # Iterate through each `of` branch. for i in 1 .. <field.len: - expectKind(field[i], nnkOfBranch) - - result.add processOfBranch(field[i], jsonNode, kindType, kindJsonNode) + case field[i].kind + of nnkOfBranch: + result.add processOfBranch(field[i], jsonNode, kindType, kindJsonNode) + of nnkElse: + result.add processElseBranch(field, field[i], jsonNode, kindType, kindJsonNode) + else: + assert false, "Expected OfBranch or Else node kinds, got: " & $field[i].kind else: assert false, "Unable to process object field: " & $field.kind @@ -1593,7 +1646,7 @@ proc postProcess(node: NimNode): NimNode = # Create the type. # -> 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 f0f0e6b56..b5d73240e 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: |