Implements else branch for JSON unmarshalling of object variants.
This commit is contained in:
parent
658467a31f
commit
a883424d0d
2 changed files with 106 additions and 19 deletions
|
|
@ -1303,8 +1303,14 @@ template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind],
|
||||||
raise newException(JsonKindError, msg)
|
raise newException(JsonKindError, msg)
|
||||||
|
|
||||||
proc getEnum(node: JsonNode, ast: string, T: typedesc): T =
|
proc getEnum(node: JsonNode, ast: string, T: typedesc): T =
|
||||||
verifyJsonKind(node, {JString}, ast)
|
when T is SomeInteger:
|
||||||
return parseEnum[T](node.getStr())
|
# 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 =
|
proc toIdentNode(typeNode: NimNode): NimNode =
|
||||||
## Converts a Sym type node (returned by getType et al.) into an
|
## Converts a Sym type node (returned by getType et al.) into an
|
||||||
|
|
@ -1322,26 +1328,32 @@ proc toIdentNode(typeNode: NimNode): NimNode =
|
||||||
else:
|
else:
|
||||||
assert false, "Cannot convert typeNode to an ident node: " & $typeNode.kind
|
assert false, "Cannot convert typeNode to an ident node: " & $typeNode.kind
|
||||||
|
|
||||||
proc createIfStmtForOf(ofBranch, jsonNode, kindType,
|
proc createGetEnumCall(jsonNode, kindType: NimNode): NimNode =
|
||||||
value: NimNode): NimNode {.compileTime.} =
|
|
||||||
## Transforms a case of branch into an if statement to be placed as the
|
|
||||||
## ExprColonExpr body expr.
|
|
||||||
expectKind(ofBranch, nnkOfBranch)
|
|
||||||
|
|
||||||
# -> getEnum(`jsonNode`, `kindType`)
|
# -> getEnum(`jsonNode`, `kindType`)
|
||||||
let getEnumSym = bindSym("getEnum")
|
let getEnumSym = bindSym("getEnum")
|
||||||
let astStrLit = toStrLit(jsonNode)
|
let astStrLit = toStrLit(jsonNode)
|
||||||
let getEnumCall = newCall(getEnumSym, jsonNode, astStrLit, kindType)
|
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:
|
for ofCond in ofBranch:
|
||||||
if ofCond.kind == nnkRecList:
|
if ofCond.kind == nnkRecList:
|
||||||
break
|
break
|
||||||
|
|
||||||
if cond.kind == nnkEmpty:
|
let comparison = infix(getEnumCall, "==", ofCond)
|
||||||
cond = infix(getEnumCall, "==", ofCond)
|
cond = infix(cond, "or", comparison)
|
||||||
else:
|
|
||||||
cond = infix(cond, "or", infix(getEnumCall, "==", ofCond))
|
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(
|
return newIfStmt(
|
||||||
(cond, value)
|
(cond, value)
|
||||||
|
|
@ -1378,6 +1390,43 @@ proc processOfBranch(ofBranch, jsonNode, kindType,
|
||||||
let ifStmt = createIfStmtForOf(ofBranch, kindJsonNode, kindType, objField[1])
|
let ifStmt = createIfStmtForOf(ofBranch, kindJsonNode, kindType, objField[1])
|
||||||
exprColonExpr.add(ifStmt)
|
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] =
|
proc processObjField(field, jsonNode: NimNode): seq[NimNode] =
|
||||||
## Process a field from a ``RecList``.
|
## Process a field from a ``RecList``.
|
||||||
##
|
##
|
||||||
|
|
@ -1418,9 +1467,13 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] =
|
||||||
|
|
||||||
# Iterate through each `of` branch.
|
# Iterate through each `of` branch.
|
||||||
for i in 1 .. <field.len:
|
for i in 1 .. <field.len:
|
||||||
expectKind(field[i], nnkOfBranch)
|
case field[i].kind
|
||||||
|
of nnkOfBranch:
|
||||||
result.add processOfBranch(field[i], jsonNode, kindType, kindJsonNode)
|
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:
|
else:
|
||||||
assert false, "Unable to process object field: " & $field.kind
|
assert false, "Unable to process object field: " & $field.kind
|
||||||
|
|
||||||
|
|
@ -1593,7 +1646,7 @@ proc postProcess(node: NimNode): NimNode =
|
||||||
|
|
||||||
# Create the type.
|
# Create the type.
|
||||||
# -> var res = Object()
|
# -> var res = Object()
|
||||||
var resIdent = newIdentNode("res")
|
var resIdent = genSym(nskVar, "res")
|
||||||
# TODO: Placing `node[0]` inside quote is buggy
|
# TODO: Placing `node[0]` inside quote is buggy
|
||||||
var resType = toIdentNode(node[0])
|
var resType = toIdentNode(node[0])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,42 @@ when isMainModule:
|
||||||
doAssert y.test3
|
doAssert y.test3
|
||||||
doAssert y.testNil.isNil
|
doAssert y.testNil.isNil
|
||||||
|
|
||||||
# TODO: Test for custom object variants (without an enum).
|
# Test for custom object variants (without an enum) and with an else branch.
|
||||||
# TODO: Test for object variant 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.
|
# Tests that verify the error messages for invalid data.
|
||||||
block:
|
block:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue