From 6d3fbf973f1b0f6cc492b67f30cea74e5b33fbf8 Mon Sep 17 00:00:00 2001 From: flaviut Date: Sun, 13 Apr 2014 17:02:18 -0400 Subject: [PATCH 1/7] Allow for nil chaining in JSON --- lib/pure/json.nim | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 7424bbae95..0ac1f98d44 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -628,16 +628,27 @@ proc len*(n: PJsonNode): int = of JObject: result = n.fields.len else: discard -proc `[]`*(node: PJsonNode, name: string): PJsonNode = - ## Gets a field from a `JObject`. Returns nil if the key is not found. +proc `[]`*(node: PJsonNode, name: string, default: PJsonNode = nil): PJsonNode = + ## Gets a field from a `JObject`. If node is nil, this returns default + ## This is to allow for chaining such as the following: + ## .. code-block Nimrod + ## foo["field1"]["field2"] + ## + ## If `field1` is undefined, it will return nil, and the access on `field2` will + ## pass through. Also returns default if the key is not found. + if isNil(node): + return default assert(node.kind == JObject) for key, item in items(node.fields): if key == name: return item - return nil + return default -proc `[]`*(node: PJsonNode, index: int): PJsonNode = - ## Gets the node at `index` in an Array. +proc `[]`*(node: PJsonNode, index: int, default: PJsonNode = nil): PJsonNode = + ## Gets the node at `index` in an Array.If `node` is nil, this returns nil to + ## allow for chaining. Results are undefined if the index is out of range. + if isNil(node): + return default assert(node.kind == JArray) return node.elems[index] From 4ff511280301d53ebac3a473f988d93ba9c065e2 Mon Sep 17 00:00:00 2001 From: flaviut Date: Sun, 13 Apr 2014 17:05:12 -0400 Subject: [PATCH 2/7] Add a couple words to docs --- lib/pure/json.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 0ac1f98d44..3e8840e470 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -635,7 +635,7 @@ proc `[]`*(node: PJsonNode, name: string, default: PJsonNode = nil): PJsonNode = ## foo["field1"]["field2"] ## ## If `field1` is undefined, it will return nil, and the access on `field2` will - ## pass through. Also returns default if the key is not found. + ## pass through and return nil. Also returns default if the key is not found. if isNil(node): return default assert(node.kind == JObject) From db7fee6303652572812a57ae70e3f2ca28e8829d Mon Sep 17 00:00:00 2001 From: flaviut Date: Sun, 13 Apr 2014 17:19:59 -0400 Subject: [PATCH 3/7] Add tests for the nil passthrough --- lib/pure/json.nim | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 3e8840e470..7b2a0eed65 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -936,6 +936,14 @@ when isMainModule: raise newException(EInvalidValue, "That line was expected to fail") except EInvalidIndex: echo() + let passthroughTest = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd" }""" + # nil passthrough + assert(passthroughTest["doesnt_exist"][1] == nil) + assert(passthroughTest["doesnt_exist"]["anything"] == nil) + # default param + assert(passthroughTest["doesnt_exist",%true].bval) + assert(passthroughTest["doesnt_exist"][1,%true].bval) + discard """ while true: var json = stdin.readLine() From 64d3b9a34d0b51a6507a9be7ebd2d6fd25d64977 Mon Sep 17 00:00:00 2001 From: flaviut Date: Sun, 13 Apr 2014 17:25:43 -0400 Subject: [PATCH 4/7] Fix subtle mistake in docs and formatting --- lib/pure/json.nim | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 7b2a0eed65..38cdcc3db1 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -629,13 +629,13 @@ proc len*(n: PJsonNode): int = else: discard proc `[]`*(node: PJsonNode, name: string, default: PJsonNode = nil): PJsonNode = - ## Gets a field from a `JObject`. If node is nil, this returns default + ## Gets a field from a `JObject`. If node is nil, this returns `default` ## This is to allow for chaining such as the following: ## .. code-block Nimrod ## foo["field1"]["field2"] ## ## If `field1` is undefined, it will return nil, and the access on `field2` will - ## pass through and return nil. Also returns default if the key is not found. + ## pass through and return `default`. Also returns `default` if the key is not found. if isNil(node): return default assert(node.kind == JObject) @@ -645,8 +645,9 @@ proc `[]`*(node: PJsonNode, name: string, default: PJsonNode = nil): PJsonNode = return default proc `[]`*(node: PJsonNode, index: int, default: PJsonNode = nil): PJsonNode = - ## Gets the node at `index` in an Array.If `node` is nil, this returns nil to - ## allow for chaining. Results are undefined if the index is out of range. + ## Gets the node at `index` in an Array.If `node` is nil, this returns `result`, + ## which is by default `nil` to allow for chaining. Results are undefined if + ## the index is out of range. if isNil(node): return default assert(node.kind == JArray) From ec59b790c52223ff8c8f3f489005df1b6e4a51b6 Mon Sep 17 00:00:00 2001 From: flaviut Date: Sun, 13 Apr 2014 17:42:52 -0400 Subject: [PATCH 5/7] JSON index accesses are bounds checked --- lib/pure/json.nim | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 38cdcc3db1..4d6ecb837b 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -644,13 +644,16 @@ proc `[]`*(node: PJsonNode, name: string, default: PJsonNode = nil): PJsonNode = return item return default -proc `[]`*(node: PJsonNode, index: int, default: PJsonNode = nil): PJsonNode = +proc `[]`*(node: PJsonNode, index: int, default: PJsonNode = nil): PJsonNode {.raises: [EInvalidIndex].}= ## Gets the node at `index` in an Array.If `node` is nil, this returns `result`, - ## which is by default `nil` to allow for chaining. Results are undefined if - ## the index is out of range. + ## which is by default `nil` to allow for chaining. `EInvalidIndex` is raised if `index` + ## is out of bounds if isNil(node): return default assert(node.kind == JArray) + if index < 0 or index >= node.elems.len: + raise newException(EInvalidIndex, + "index " & $index & " is out of range [0, " & $node.elems.len & ")") return node.elems[index] proc hasKey*(node: PJsonNode, key: string): bool = @@ -937,13 +940,29 @@ when isMainModule: raise newException(EInvalidValue, "That line was expected to fail") except EInvalidIndex: echo() - let passthroughTest = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd" }""" + let testJson = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd" }""" # nil passthrough - assert(passthroughTest["doesnt_exist"][1] == nil) - assert(passthroughTest["doesnt_exist"]["anything"] == nil) + assert(testJson["doesnt_exist"][1] == nil) + assert(testJson["doesnt_exist"]["anything"] == nil) # default param - assert(passthroughTest["doesnt_exist",%true].bval) - assert(passthroughTest["doesnt_exist"][1,%true].bval) + assert(testJson["doesnt_exist",%true].bval) + assert(testJson["doesnt_exist"][1,%true].bval) + + # Bounds checking + try: + let a = testJson["a"][9] + assert(false, "EInvalidIndex not thrown") + except EInvalidIndex: + discard + try: + let a = testJson["a"][-1] + assert(false, "EInvalidIndex not thrown") + except EInvalidIndex: + discard + try: + assert(testJson["a"][0].num == 1, "Index doesn't correspond to its value") + except: + assert(false, "EInvalidIndex thrown for valid index") discard """ while true: From c7a57dc184ba5757c34884da44ed1b75dc150101 Mon Sep 17 00:00:00 2001 From: flaviut Date: Mon, 26 May 2014 16:13:19 -0400 Subject: [PATCH 6/7] Switch chaining to the `{}` and `{}=` operators --- lib/pure/json.nim | 56 +++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 4d6ecb837b..a450a340cb 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -628,29 +628,26 @@ proc len*(n: PJsonNode): int = of JObject: result = n.fields.len else: discard -proc `[]`*(node: PJsonNode, name: string, default: PJsonNode = nil): PJsonNode = - ## Gets a field from a `JObject`. If node is nil, this returns `default` - ## This is to allow for chaining such as the following: - ## .. code-block Nimrod - ## foo["field1"]["field2"] - ## - ## If `field1` is undefined, it will return nil, and the access on `field2` will - ## pass through and return `default`. Also returns `default` if the key is not found. +proc `[]`*(node: PJsonNode, name: string): PJsonNode + {.raises: [EInvalidValue].} = + ## Gets a field from a `JObject`. If `node` is nil, raises an + ## `EInvalidValue` exception. If the value at `name` does not + ## exist, returns nil if isNil(node): - return default + raise newException(EInvalidValue, "node may not be nil") assert(node.kind == JObject) for key, item in items(node.fields): if key == name: return item - return default + return nil -proc `[]`*(node: PJsonNode, index: int, default: PJsonNode = nil): PJsonNode {.raises: [EInvalidIndex].}= - ## Gets the node at `index` in an Array.If `node` is nil, this returns `result`, - ## which is by default `nil` to allow for chaining. `EInvalidIndex` is raised if `index` +proc `[]`*(node: PJsonNode, index: int): PJsonNode + {.raises: [EInvalidIndex, EInvalidValue].} = + ## Gets the node at `index` in an Array. `EInvalidIndex` is raised if `index` ## is out of bounds - if isNil(node): - return default assert(node.kind == JArray) + if isNil(node): + raise newException(EInvalidValue, "node may not be nil") if index < 0 or index >= node.elems.len: raise newException(EInvalidIndex, "index " & $index & " is out of range [0, " & $node.elems.len & ")") @@ -686,6 +683,25 @@ proc `[]=`*(obj: PJsonNode, key: string, val: PJsonNode) = return obj.fields.add((key, val)) +proc `{}`*(node: PJsonNode, names: varargs[string]): PJsonNode = + ## Transverses the node and gets the given value. If any of the + ## names does not exist, returns nil + result = node + for name in names: + result = result[name] + if isNil(result): + return nil + +proc `{}=`*(node: PJsonNode, names: varargs[string], value: PJsonNode) = + ## Transverses the node and tries to set the value at the given location + ## to `value` If any of the names are missing, they are added + var node = node + for i in 0..(names.len-2): + if isNil(node[names[i]]): + node[names[i]] = newJObject() + node = node[names[i]] + node[names[names.len-1]] = value + proc delete*(obj: PJsonNode, key: string) = ## Deletes ``obj[key]`` preserving the order of the other (key, value)-pairs. assert(obj.kind == JObject) @@ -942,12 +958,10 @@ when isMainModule: let testJson = parseJson"""{ "a": [1, 2, 3, 4], "b": "asd" }""" # nil passthrough - assert(testJson["doesnt_exist"][1] == nil) - assert(testJson["doesnt_exist"]["anything"] == nil) - # default param - assert(testJson["doesnt_exist",%true].bval) - assert(testJson["doesnt_exist"][1,%true].bval) - + assert(testJson{"doesnt_exist", "anything"} == nil) + testJson{["c", "d"]} = %true + assert(testJson["c"]["d"].bval) + # Bounds checking try: let a = testJson["a"][9] From 08a76f04664020e7bdf99f3732a808d40c37350d Mon Sep 17 00:00:00 2001 From: flaviut Date: Sat, 31 May 2014 14:13:29 -0400 Subject: [PATCH 7/7] replace nil checks with assert --- lib/pure/json.nim | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/pure/json.nim b/lib/pure/json.nim index a450a340cb..636a333134 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -628,29 +628,21 @@ proc len*(n: PJsonNode): int = of JObject: result = n.fields.len else: discard -proc `[]`*(node: PJsonNode, name: string): PJsonNode - {.raises: [EInvalidValue].} = - ## Gets a field from a `JObject`. If `node` is nil, raises an - ## `EInvalidValue` exception. If the value at `name` does not - ## exist, returns nil - if isNil(node): - raise newException(EInvalidValue, "node may not be nil") +proc `[]`*(node: PJsonNode, name: string): PJsonNode = + ## Gets a field from a `JObject`, which must not be nil. + ## If the value at `name` does not exist, returns nil + assert(node != nil) assert(node.kind == JObject) for key, item in items(node.fields): if key == name: return item return nil -proc `[]`*(node: PJsonNode, index: int): PJsonNode - {.raises: [EInvalidIndex, EInvalidValue].} = - ## Gets the node at `index` in an Array. `EInvalidIndex` is raised if `index` +proc `[]`*(node: PJsonNode, index: int): PJsonNode = + ## Gets the node at `index` in an Array. Result is undefined if `index` ## is out of bounds assert(node.kind == JArray) - if isNil(node): - raise newException(EInvalidValue, "node may not be nil") - if index < 0 or index >= node.elems.len: - raise newException(EInvalidIndex, - "index " & $index & " is out of range [0, " & $node.elems.len & ")") + assert(node != nil) return node.elems[index] proc hasKey*(node: PJsonNode, key: string): bool =