diff options
Diffstat (limited to 'lib/pure/json.nim')
-rw-r--r-- | lib/pure/json.nim | 158 |
1 files changed, 86 insertions, 72 deletions
diff --git a/lib/pure/json.nim b/lib/pure/json.nim index ab7d18bd8..30004da84 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -51,7 +51,10 @@ ## ] import - hashes, strutils, lexbase, streams, unicode, macros + hashes, tables, strutils, lexbase, streams, unicode, macros + +export + tables.`$` type JsonEventKind* = enum ## enumeration of all events that may occur when parsing @@ -567,7 +570,7 @@ type of JNull: nil of JObject: - fields*: seq[tuple[key: string, val: JsonNode]] + fields*: Table[string, JsonNode] of JArray: elems*: seq[JsonNode] @@ -617,7 +620,7 @@ proc newJObject*(): JsonNode = ## Creates a new `JObject JsonNode` new(result) result.kind = JObject - result.fields = @[] + result.fields = initTable[string, JsonNode](4) proc newJArray*(): JsonNode = ## Creates a new `JArray JsonNode` @@ -657,8 +660,8 @@ proc getBVal*(n: JsonNode, default: bool = false): bool = else: return n.bval proc getFields*(n: JsonNode, - default: seq[tuple[key: string, val: JsonNode]] = @[]): - seq[tuple[key: string, val: JsonNode]] = + default = initTable[string, JsonNode](4)): + Table[string, JsonNode] = ## Retrieves the key, value pairs of a `JObject JsonNode`. ## ## Returns ``default`` if ``n`` is not a ``JObject``, or if ``n`` is nil. @@ -675,6 +678,7 @@ proc getElems*(n: JsonNode, default: seq[JsonNode] = @[]): seq[JsonNode] = proc `%`*(s: string): JsonNode = ## Generic constructor for JSON data. Creates a new `JString JsonNode`. new(result) + if s.isNil: return result.kind = JString result.str = s @@ -700,8 +704,8 @@ proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode = ## Generic constructor for JSON data. Creates a new `JObject JsonNode` new(result) result.kind = JObject - newSeq(result.fields, keyVals.len) - for i, p in pairs(keyVals): result.fields[i] = p + result.fields = initTable[string, JsonNode](4) + for key, val in items(keyVals): result.fields[key] = val proc `%`*(elements: openArray[JsonNode]): JsonNode = ## Generic constructor for JSON data. Creates a new `JArray JsonNode` @@ -712,17 +716,21 @@ proc `%`*(elements: openArray[JsonNode]): JsonNode = proc toJson(x: NimNode): NimNode {.compiletime.} = case x.kind - of nnkBracket: + of nnkBracket: # array result = newNimNode(nnkBracket) for i in 0 .. <x.len: result.add(toJson(x[i])) - of nnkTableConstr: + of nnkTableConstr: # object result = newNimNode(nnkTableConstr) for i in 0 .. <x.len: - assert x[i].kind == nnkExprColonExpr + x[i].expectKind nnkExprColonExpr result.add(newNimNode(nnkExprColonExpr).add(x[i][0]).add(toJson(x[i][1]))) + of nnkCurly: # empty object + result = newNimNode(nnkTableConstr) + x.expectLen(0) + else: result = x @@ -757,7 +765,9 @@ proc `==`* (a,b: JsonNode): bool = of JObject: a.fields == b.fields -proc hash* (n:JsonNode): Hash = +proc hash*(n: Table[string, JsonNode]): Hash {.noSideEffect.} + +proc hash*(n: JsonNode): Hash = ## Compute the hash for a JSON node case n.kind of JArray: @@ -775,6 +785,11 @@ proc hash* (n:JsonNode): Hash = of JNull: result = hash(0) +proc hash*(n: Table[string, JsonNode]): Hash = + for key, val in n: + result = result !& hash(key) !& hash(val) + result = !$result + proc len*(n: JsonNode): int = ## If `n` is a `JArray`, it returns the number of elements. ## If `n` is a `JObject`, it returns the number of pairs. @@ -789,10 +804,7 @@ proc `[]`*(node: JsonNode, name: string): JsonNode {.inline.} = ## If the value at `name` does not exist, returns nil assert(not isNil(node)) assert(node.kind == JObject) - for key, item in items(node.fields): - if key == name: - return item - return nil + result = node.fields.getOrDefault(name) proc `[]`*(node: JsonNode, index: int): JsonNode {.inline.} = ## Gets the node at `index` in an Array. Result is undefined if `index` @@ -804,8 +816,7 @@ proc `[]`*(node: JsonNode, index: int): JsonNode {.inline.} = proc hasKey*(node: JsonNode, key: string): bool = ## Checks if `key` exists in `node`. assert(node.kind == JObject) - for k, item in items(node.fields): - if k == key: return true + result = node.fields.hasKey(key) proc existsKey*(node: JsonNode, key: string): bool {.deprecated.} = node.hasKey(key) ## Deprecated for `hasKey` @@ -816,20 +827,14 @@ proc add*(father, child: JsonNode) = father.elems.add(child) proc add*(obj: JsonNode, key: string, val: JsonNode) = - ## Adds ``(key, val)`` pair to the JObject node `obj`. For speed - ## reasons no check for duplicate keys is performed! - ## But ``[]=`` performs the check. + ## Sets a field from a `JObject`. assert obj.kind == JObject - obj.fields.add((key, val)) + obj.fields[key] = val proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} = - ## Sets a field from a `JObject`. Performs a check for duplicate keys. + ## Sets a field from a `JObject`. assert(obj.kind == JObject) - for i in 0..obj.fields.len-1: - if obj.fields[i].key == key: - obj.fields[i].val = val - return - obj.fields.add((key, val)) + obj.fields[key] = val proc `{}`*(node: JsonNode, keys: varargs[string]): JsonNode = ## Traverses the node and gets the given value. If any of the @@ -852,13 +857,11 @@ proc `{}=`*(node: JsonNode, keys: varargs[string], value: JsonNode) = node[keys[keys.len-1]] = value proc delete*(obj: JsonNode, key: string) = - ## Deletes ``obj[key]`` preserving the order of the other (key, value)-pairs. + ## Deletes ``obj[key]``. assert(obj.kind == JObject) - for i in 0..obj.fields.len-1: - if obj.fields[i].key == key: - obj.fields.delete(i) - return - raise newException(IndexError, "key not in object") + if not obj.fields.hasKey(key): + raise newException(IndexError, "key not in object") + obj.fields.del(key) proc copy*(p: JsonNode): JsonNode = ## Performs a deep copy of `a`. @@ -875,8 +878,8 @@ proc copy*(p: JsonNode): JsonNode = result = newJNull() of JObject: result = newJObject() - for key, field in items(p.fields): - result.fields.add((key, copy(field))) + for key, val in pairs(p.fields): + result.fields[key] = copy(val) of JArray: result = newJArray() for i in items(p.elems): @@ -920,15 +923,17 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true, if node.fields.len > 0: result.add("{") result.nl(ml) # New line - for i in 0..len(node.fields)-1: + var i = 0 + for key, val in pairs(node.fields): if i > 0: result.add(", ") result.nl(ml) # New Line + inc i # Need to indent more than { result.indent(newIndent(currIndent, indent, ml)) - result.add(escapeJson(node.fields[i].key)) + result.add(escapeJson(key)) result.add(": ") - toPretty(result, node.fields[i].val, indent, ml, false, + toPretty(result, val, indent, ml, false, newIndent(currIndent, indent, ml)) result.nl(ml) result.indent(currIndent) # indent the same as { @@ -990,7 +995,7 @@ proc toUgly*(result: var string, node: JsonNode) = result.add "]" of JObject: result.add "{" - for key, value in items(node.fields): + for key, value in pairs(node.fields): if comma: result.add "," else: comma = true result.add key.escapeJson() @@ -1029,15 +1034,15 @@ iterator mitems*(node: var JsonNode): var JsonNode = iterator pairs*(node: JsonNode): tuple[key: string, val: JsonNode] = ## Iterator for the child elements of `node`. `node` has to be a JObject. assert node.kind == JObject - for key, val in items(node.fields): + for key, val in pairs(node.fields): yield (key, val) -iterator mpairs*(node: var JsonNode): var tuple[key: string, val: JsonNode] = +iterator mpairs*(node: var JsonNode): tuple[key: string, val: var JsonNode] = ## Iterator for the child elements of `node`. `node` has to be a JObject. - ## Items can be modified + ## Values can be modified assert node.kind == JObject - for keyVal in mitems(node.fields): - yield keyVal + for key, val in mpairs(node.fields): + yield (key, val) proc eat(p: var JsonParser, tok: TokKind) = if p.tok == tok: discard getTok(p) @@ -1119,9 +1124,9 @@ else: proc parseNativeJson(x: cstring): JSObject {.importc: "JSON.parse".} - proc getVarType(x): JsonNodeKind = + proc getVarType(x: JSObject): JsonNodeKind = result = JNull - proc getProtoName(y): cstring + proc getProtoName(y: JSObject): cstring {.importc: "Object.prototype.toString.call".} case $getProtoName(x) # TODO: Implicit returns fail here. of "[object Array]": return JArray @@ -1212,23 +1217,27 @@ when false: # To get that we shall use, obj["json"] when isMainModule: - var parsed = parseFile("tests/testdata/jsontest.json") - var parsed2 = parseFile("tests/testdata/jsontest2.json") + when not defined(js): + var parsed = parseFile("tests/testdata/jsontest.json") - try: - discard parsed["key2"][12123] - assert(false) - except IndexError: assert(true) + try: + discard parsed["key2"][12123] + doAssert(false) + except IndexError: doAssert(true) + + var parsed2 = parseFile("tests/testdata/jsontest2.json") + doAssert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}") let testJson = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd", "c": "\ud83c\udf83", "d": "\u00E6"}""" # nil passthrough - assert(testJson{"doesnt_exist"}{"anything"}.isNil) + doAssert(testJson{"doesnt_exist"}{"anything"}.isNil) testJson{["e", "f"]} = %true - assert(testJson["e"]["f"].bval) + doAssert(testJson["e"]["f"].bval) # make sure UTF-16 decoding works. - assert(testJson["c"].str == "🎃") - assert(testJson["d"].str == "æ") + when not defined(js): # TODO: The following line asserts in JS + doAssert(testJson["c"].str == "🎃") + doAssert(testJson["d"].str == "æ") # make sure no memory leek when parsing invalid string let startMemory = getOccupiedMem() @@ -1238,41 +1247,43 @@ when isMainModule: except: discard # memory diff should less than 2M - assert(abs(getOccupiedMem() - startMemory) < 2 * 1024 * 1024) + doAssert(abs(getOccupiedMem() - startMemory) < 2 * 1024 * 1024) # test `$` let stringified = $testJson let parsedAgain = parseJson(stringified) - assert(parsedAgain["b"].str == "asd") + doAssert(parsedAgain["b"].str == "asd") + + parsedAgain["abc"] = %5 + doAssert parsedAgain["abc"].num == 5 # Bounds checking try: let a = testJson["a"][9] - assert(false, "EInvalidIndex not thrown") + doAssert(false, "EInvalidIndex not thrown") except IndexError: discard try: let a = testJson["a"][-1] - assert(false, "EInvalidIndex not thrown") + doAssert(false, "EInvalidIndex not thrown") except IndexError: discard try: - assert(testJson["a"][0].num == 1, "Index doesn't correspond to its value") + doAssert(testJson["a"][0].num == 1, "Index doesn't correspond to its value") except: - assert(false, "EInvalidIndex thrown for valid index") + doAssert(false, "EInvalidIndex thrown for valid index") - assert(testJson{"b"}.str=="asd", "Couldn't fetch a singly nested key with {}") - assert(isNil(testJson{"nonexistent"}), "Non-existent keys should return nil") - assert(parsed2{"repository", "description"}.str=="IRC Library for Haskell", "Couldn't fetch via multiply nested key using {}") - assert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil") - assert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil") - assert(testJson{"a"}==parseJson"[1, 2, 3, 4]", "Didn't return a non-JObject when there was one to be found") - assert(isNil(parseJson("[1, 2, 3]"){"foo"}), "Indexing directly into a list should return nil") + doAssert(testJson{"b"}.str=="asd", "Couldn't fetch a singly nested key with {}") + doAssert(isNil(testJson{"nonexistent"}), "Non-existent keys should return nil") + doAssert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil") + doAssert(isNil(testJson{"a", "b"}), "Indexing through a list should return nil") + doAssert(testJson{"a"}==parseJson"[1, 2, 3, 4]", "Didn't return a non-JObject when there was one to be found") + doAssert(isNil(parseJson("[1, 2, 3]"){"foo"}), "Indexing directly into a list should return nil") # Generator: var j = %* [{"name": "John", "age": 30}, {"name": "Susan", "age": 31}] - assert j == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}] + doAssert j == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}] var j2 = %* [ @@ -1285,7 +1296,7 @@ when isMainModule: "age": 31 } ] - assert j2 == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}] + doAssert j2 == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}] var name = "John" let herAge = 30 @@ -1299,4 +1310,7 @@ when isMainModule: , "age": hisAge } ] - assert j3 == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}] + doAssert j3 == %[%{"name": %"John", "age": %30}, %{"name": %"Susan", "age": %31}] + + var j4 = %*{"test": nil} + doAssert j4 == %{"test": newJNull()} |