diff options
Diffstat (limited to 'lib/pure/json.nim')
-rw-r--r-- | lib/pure/json.nim | 143 |
1 files changed, 85 insertions, 58 deletions
diff --git a/lib/pure/json.nim b/lib/pure/json.nim index e3d5191c6..3d86cc9d7 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -37,15 +37,15 @@ ## Retrieving the value of a JSON node can then be achieved using one of the ## helper procedures, which include: ## -## * ``getNum`` -## * ``getFNum`` +## * ``getInt`` +## * ``getFloat`` ## * ``getStr`` -## * ``getBVal`` +## * ``getBool`` ## ## To retrieve the value of ``"key"`` you can do the following: ## ## .. code-block:: Nim -## doAssert jsonNode["key"].getFNum() == 3.14 +## doAssert jsonNode["key"].getFloat() == 3.14 ## ## The ``[]`` operator will raise an exception when the specified field does ## not exist. If you wish to avoid this behaviour you can use the ``{}`` @@ -681,14 +681,25 @@ proc getStr*(n: JsonNode, default: string = ""): string = if n.isNil or n.kind != JString: return default else: return n.str -proc getNum*(n: JsonNode, default: BiggestInt = 0): BiggestInt = +proc getInt*(n: JsonNode, default: int = 0): int = ## Retrieves the int value of a `JInt JsonNode`. ## ## Returns ``default`` if ``n`` is not a ``JInt``, or if ``n`` is nil. if n.isNil or n.kind != JInt: return default + else: return int(n.num) + +proc getBiggestInt*(n: JsonNode, default: BiggestInt = 0): BiggestInt = + ## Retrieves the BiggestInt value of a `JInt JsonNode`. + ## + ## Returns ``default`` if ``n`` is not a ``JInt``, or if ``n`` is nil. + if n.isNil or n.kind != JInt: return default else: return n.num -proc getFNum*(n: JsonNode, default: float = 0.0): float = +proc getNum*(n: JsonNode, default: BiggestInt = 0): BiggestInt {.deprecated.} = + ## Deprecated - use getInt or getBiggestInt instead + getBiggestInt(n, default) + +proc getFloat*(n: JsonNode, default: float = 0.0): float = ## Retrieves the float value of a `JFloat JsonNode`. ## ## Returns ``default`` if ``n`` is not a ``JFloat`` or ``JInt``, or if ``n`` is nil. @@ -698,13 +709,21 @@ proc getFNum*(n: JsonNode, default: float = 0.0): float = of JInt: return float(n.num) else: return default -proc getBVal*(n: JsonNode, default: bool = false): bool = +proc getFNum*(n: JsonNode, default: float = 0.0): float {.deprecated.} = + ## Deprecated - use getFloat instead + getFloat(n, default) + +proc getBool*(n: JsonNode, default: bool = false): bool = ## Retrieves the bool value of a `JBool JsonNode`. ## ## Returns ``default`` if ``n`` is not a ``JBool``, or if ``n`` is nil. if n.isNil or n.kind != JBool: return default else: return n.bval +proc getBVal*(n: JsonNode, default: bool = false): bool {.deprecated.} = + ## Deprecated - use getBVal instead + getBool(n, default) + proc getFields*(n: JsonNode, default = initOrderedTable[string, JsonNode](4)): OrderedTable[string, JsonNode] = @@ -721,6 +740,16 @@ proc getElems*(n: JsonNode, default: seq[JsonNode] = @[]): seq[JsonNode] = if n.isNil or n.kind != JArray: return default else: return n.elems +proc add*(father, child: JsonNode) = + ## Adds `child` to a JArray node `father`. + assert father.kind == JArray + father.elems.add(child) + +proc add*(obj: JsonNode, key: string, val: JsonNode) = + ## Sets a field from a `JObject`. + assert obj.kind == JObject + obj.fields[key] = val + proc `%`*(s: string): JsonNode = ## Generic constructor for JSON data. Creates a new `JString JsonNode`. new(result) @@ -759,6 +788,19 @@ proc `%`*[T](elements: openArray[T]): JsonNode = result = newJArray() for elem in elements: result.add(%elem) +when false: + # For 'consistency' we could do this, but that only pushes people further + # into that evil comfort zone where they can use Nim without understanding it + # causing problems later on. + proc `%`*(elements: set[bool]): JsonNode = + ## Generic constructor for JSON data. Creates a new `JObject JsonNode`. + ## This can only be used with the empty set ``{}`` and is supported + ## to prevent the gotcha ``%*{}`` which used to produce an empty + ## JSON array. + result = newJObject() + assert false notin elements, "usage error: only empty sets allowed" + assert true notin elements, "usage error: only empty sets allowed" + proc `%`*(o: object): JsonNode = ## Generic constructor for JSON data. Creates a new `JObject JsonNode` result = newJObject() @@ -779,27 +821,25 @@ proc `%`*(o: enum): JsonNode = proc toJson(x: NimNode): NimNode {.compiletime.} = case x.kind of nnkBracket: # array + if x.len == 0: return newCall(bindSym"newJArray") result = newNimNode(nnkBracket) - for i in 0 .. <x.len: + for i in 0 ..< x.len: result.add(toJson(x[i])) - + result = newCall(bindSym"%", result) of nnkTableConstr: # object + if x.len == 0: return newCall(bindSym"newJObject") result = newNimNode(nnkTableConstr) - for i in 0 .. <x.len: + for i in 0 ..< x.len: x[i].expectKind nnkExprColonExpr - result.add(newNimNode(nnkExprColonExpr).add(x[i][0]).add(toJson(x[i][1]))) - + result.add newTree(nnkExprColonExpr, x[i][0], toJson(x[i][1])) + result = newCall(bindSym"%", result) of nnkCurly: # empty object - result = newNimNode(nnkTableConstr) x.expectLen(0) - + result = newCall(bindSym"newJObject") of nnkNilLit: - result = newCall("newJNull") - + result = newCall(bindSym"newJNull") else: - result = x - - result = prefix(result, "%") + result = newCall(bindSym"%", x) macro `%*`*(x: untyped): untyped = ## Convert an expression to a JsonNode directly, without having to specify @@ -909,16 +949,6 @@ proc contains*(node: JsonNode, val: JsonNode): bool = proc existsKey*(node: JsonNode, key: string): bool {.deprecated.} = node.hasKey(key) ## Deprecated for `hasKey` -proc add*(father, child: JsonNode) = - ## Adds `child` to a JArray node `father`. - assert father.kind == JArray - father.elems.add(child) - -proc add*(obj: JsonNode, key: string, val: JsonNode) = - ## Sets a field from a `JObject`. - assert obj.kind == JObject - obj.fields[key] = val - proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} = ## Sets a field from a `JObject`. assert(obj.kind == JObject) @@ -996,24 +1026,17 @@ proc nl(s: var string, ml: bool) = proc escapeJson*(s: string; result: var string) = ## Converts a string `s` to its JSON representation. ## Appends to ``result``. - const - HexChars = "0123456789ABCDEF" result.add("\"") - for x in runes(s): - var r = int(x) - if r >= 32 and r <= 126: - var c = chr(r) - case c - of '"': result.add("\\\"") - of '\\': result.add("\\\\") - else: result.add(c) - else: - # toHex inlined for more speed (saves stupid string allocations): - result.add("\\u0000") - let start = result.len - 4 - for j in countdown(3, 0): - result[j+start] = HexChars[r and 0xF] - r = r shr 4 + for c in s: + case c + of '\L': result.add("\\n") + of '\b': result.add("\\b") + of '\f': result.add("\\f") + of '\t': result.add("\\t") + of '\r': result.add("\\r") + of '"': result.add("\\\"") + of '\\': result.add("\\\\") + else: result.add(c) result.add("\"") proc escapeJson*(s: string): string = @@ -1210,7 +1233,7 @@ proc parseJson(p: var JsonParser): JsonNode = raiseParseErr(p, "{") when not defined(js): - proc parseJson*(s: Stream, filename: string): JsonNode = + proc parseJson*(s: Stream, filename: string = ""): JsonNode = ## Parses from a stream `s` into a `JsonNode`. `filename` is only needed ## for nice error messages. ## If `s` contains extra data, it will raise `JsonParsingError`. @@ -1280,11 +1303,11 @@ else: case getVarType(x) of JArray: result = newJArray() - for i in 0 .. <x.len: + for i in 0 ..< x.len: result.add(x[i].convertObject()) of JObject: result = newJObject() - asm """for (property in `x`) { + asm """for (var property in `x`) { if (`x`.hasOwnProperty(property)) { """ var nimProperty: cstring @@ -1338,7 +1361,7 @@ proc getEnum(node: JsonNode, ast: string, T: typedesc): T = # TODO: I shouldn't need this proc. proc convert[T](x: BiggestInt): T = T(x) verifyJsonKind(node, {JInt}, ast) - return convert[T](node.getNum()) + return convert[T](node.getBiggestInt()) else: verifyJsonKind(node, {JString}, ast) return parseEnum[T](node.getStr()) @@ -1426,7 +1449,7 @@ proc processElseBranch(recCaseNode, elseBranch, jsonNode, 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): + for i in 1 ..< len(recCaseNode): if recCaseNode[i].kind == nnkElse: break @@ -1488,7 +1511,7 @@ proc processObjField(field, jsonNode: NimNode): seq[NimNode] = exprColonExpr.add(getEnumCall) # Iterate through each `of` branch. - for i in 1 .. <field.len: + for i in 1 ..< field.len: case field[i].kind of nnkOfBranch: result.add processOfBranch(field[i], jsonNode, kindType, kindJsonNode) @@ -1617,7 +1640,7 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = ( var list: `typeSym` = @[]; verifyJsonKind(`jsonNode`, {JArray}, astToStr(`jsonNode`)); - for `forLoopI` in 0 .. <`jsonNode`.len: list.add(`constructorNode`); + for `forLoopI` in 0 ..< `jsonNode`.len: list.add(`constructorNode`); list ) of "array": @@ -1631,7 +1654,7 @@ proc createConstructor(typeSym, jsonNode: NimNode): NimNode = ( var list: `typeSym`; verifyJsonKind(`jsonNode`, {JArray}, astToStr(`jsonNode`)); - for `forLoopI` in 0 .. <`jsonNode`.len: list[`forLoopI`] =`constructorNode`; + for `forLoopI` in 0 ..< `jsonNode`.len: list[`forLoopI`] =`constructorNode`; list ) @@ -1660,7 +1683,7 @@ proc postProcessValue(value: NimNode): NimNode = result = postProcess(value) else: result = value - for i in 0 .. <len(result): + for i in 0 ..< len(result): result[i] = postProcessValue(result[i]) proc postProcessExprColonExpr(exprColonExpr, resIdent: NimNode): NimNode = @@ -1740,6 +1763,7 @@ macro to*(node: JsonNode, T: typedesc): untyped = ## ## * Heterogeneous arrays are not supported. ## * Sets in object variants are not supported. + ## * Not nil annotations are not supported. ## ## Example: ## @@ -1815,8 +1839,7 @@ when isMainModule: doAssert(testJson["e"]["f"].bval) # make sure UTF-16 decoding works. - when not defined(js): # TODO: The following line asserts in JS - doAssert(testJson["c"].str == "🎃") + doAssert(testJson["c"].str == "🎃") doAssert(testJson["d"].str == "æ") # make sure no memory leek when parsing invalid string @@ -1925,7 +1948,7 @@ when isMainModule: var parsed2 = parseFile("tests/testdata/jsontest2.json") doAssert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}") - doAssert escapeJson("\10FoobarÄ") == "\"\\u000AFoobar\\u00C4\"" + doAssert escapeJson("\10Foo🎃barÄ") == "\"\\nFoo🎃barÄ\"" # Test with extra data when not defined(js): @@ -1941,4 +1964,8 @@ when isMainModule: except JsonParsingError: doAssert getCurrentExceptionMsg().contains(errorMessages[errEofExpected]) + # bug #6438 + doAssert($ %*[] == "[]") + doAssert($ %*{} == "{}") + echo("Tests succeeded!") |