diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2014-11-03 02:03:30 +0100 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2014-11-03 02:03:30 +0100 |
commit | db228d3b5cf76b37b47a3dbd7d82636955fb27be (patch) | |
tree | 61db16af77363ac890516cac15cfd432ef6c042a /lib/pure | |
parent | a0ecfd19be45f0f3901a4db36ae010f4dc225197 (diff) | |
parent | 57dadb35dc668c269d0734639bd84d95998a1f19 (diff) | |
download | Nim-db228d3b5cf76b37b47a3dbd7d82636955fb27be.tar.gz |
Merge pull request #1553 from gradha/pr_json_module_improvements
json module improvements
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/json.nim | 631 |
1 files changed, 540 insertions, 91 deletions
diff --git a/lib/pure/json.nim b/lib/pure/json.nim index a45900f29..5d51c2d87 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -7,47 +7,71 @@ # distribution, for details about the copyright. # -## This module implements a simple high performance `JSON`:idx: -## parser. JSON (JavaScript Object Notation) is a lightweight -## data-interchange format that is easy for humans to read and write -## (unlike XML). It is easy for machines to parse and generate. -## JSON is based on a subset of the JavaScript Programming Language, -## Standard ECMA-262 3rd Edition - December 1999. +## This module implements a simple high performance `JSON`:idx: parser. `JSON +## (JavaScript Object Notation) <http://www.json.org>`_ is a lightweight +## data-interchange format that is easy for humans to read and write (unlike +## XML). It is easy for machines to parse and generate. JSON is based on a +## subset of the JavaScript Programming Language, `Standard ECMA-262 3rd +## Edition - December 1999 +## <http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf>`_. ## -## Usage example: +## Parsing small values quickly can be done with the convenience `parseJson() +## <#parseJson,string>`_ proc which returns the whole JSON tree. If you are +## parsing very big JSON inputs or want to skip most of the items in them you +## can initialize your own `TJsonParser <#TJsonParser>`_ with the `open() +## <#open>`_ proc and call `next() <#next>`_ in a loop to process the +## individual parsing events. +## +## If you need to create JSON objects from your Nimrod types you can call procs +## like `newJObject() <#newJObject>`_ (or their equivalent `%() +## <#%,openArray[tuple[string,PJsonNode]]>`_ generic constructor). For +## consistency you can provide your own ``%`` operators for custom object +## types: ## ## .. code-block:: nimrod -## let -## small_json = """{"test": 1.3, "key2": true}""" -## jobj = parseJson(small_json) -## assert (jobj.kind == JObject) -## echo($jobj["test"].fnum) -## echo($jobj["key2"].bval) +## type +## Person = object ## Generic person record. +## age: int ## The age of the person. +## name: string ## The name of the person. ## -## Results in: +## proc `%`(p: Person): PJsonNode = +## ## Converts a Person into a PJsonNode. +## result = %[("age", %p.age), ("name", %p.name)] ## -## .. code-block:: nimrod +## proc test() = +## # Tests making some jsons. +## var p: Person +## p.age = 24 +## p.name = "Minah" +## echo(%p) # { "age": 24, "name": "Minah"} +## +## p.age = 33 +## p.name = "Sojin" +## echo(%p) # { "age": 33, "name": "Sojin"} ## -## 1.3000000000000000e+00 -## true +## If you don't need special logic in your Nimrod objects' serialization code +## you can also use the `marshal module <marshal.html>`_ which converts objects +## directly to JSON. import hashes, strutils, lexbase, streams, unicode type - TJsonEventKind* = enum ## enumeration of all events that may occur when parsing - jsonError, ## an error ocurred during parsing - jsonEof, ## end of file reached - jsonString, ## a string literal - jsonInt, ## an integer literal - jsonFloat, ## a float literal - jsonTrue, ## the value ``true`` - jsonFalse, ## the value ``false`` - jsonNull, ## the value ``null`` - jsonObjectStart, ## start of an object: the ``{`` token - jsonObjectEnd, ## end of an object: the ``}`` token - jsonArrayStart, ## start of an array: the ``[`` token - jsonArrayEnd ## start of an array: the ``]`` token + TJsonEventKind* = enum ## Events that may occur when parsing. \ + ## + ## You compare these values agains the result of the `kind() proc <#kind>`_. + jsonError, ## An error ocurred during parsing. + jsonEof, ## End of file reached. + jsonString, ## A string literal. + jsonInt, ## An integer literal. + jsonFloat, ## A float literal. + jsonTrue, ## The value ``true``. + jsonFalse, ## The value ``false``. + jsonNull, ## The value ``null``. + jsonObjectStart, ## Start of an object: the ``{`` token. + jsonObjectEnd, ## End of an object: the ``}`` token. + jsonArrayStart, ## Start of an array: the ``[`` token. + jsonArrayEnd ## Start of an array: the ``]`` token. TTokKind = enum # must be synchronized with TJsonEventKind! tkError, @@ -65,7 +89,7 @@ type tkColon, tkComma - TJsonError* = enum ## enumeration that lists all errors that can occur + TJsonError = enum ## enumeration that lists all errors that can occur errNone, ## no error errInvalidToken, ## invalid token errStringExpected, ## string expected @@ -82,7 +106,9 @@ type stateEof, stateStart, stateObject, stateArray, stateExpectArrayComma, stateExpectObjectComma, stateExpectColon, stateExpectValue - TJsonParser* = object of TBaseLexer ## the parser object. + TJsonParser* = object of TBaseLexer ## The JSON parser object. \ + ## + ## Create a variable of this type and use `open() <#open>`_ on it. a: string tok: TTokKind kind: TJsonEventKind @@ -117,59 +143,129 @@ const ] proc open*(my: var TJsonParser, input: PStream, filename: string) = - ## initializes the parser with an input stream. `Filename` is only used - ## for nice error messages. - lexbase.open(my, input) + ## Initializes the JSON parser with an `input stream <streams.html>`_. + ## + ## The `filename` parameter is not strictly required and is used only for + ## nice error messages. You can pass ``nil`` as long as you never use procs + ## like `errorMsg() <#errorMsg>`_ or `errorMsgExpected() + ## <#errorMsgExpected>`_ but passing a dummy filename like ``<input string>`` + ## is safer and more user friendly. Example: + ## + ## .. code-block:: nimrod + ## import json, streams + ## + ## var + ## s = newStringStream("some valid json") + ## p: TJsonParser + ## p.open(s, "<input string>") + ## + ## Once opened, you can process JSON parsing events with the `next() + ## <#next>`_ proc. my.filename = filename my.state = @[stateStart] my.kind = jsonError my.a = "" -proc close*(my: var TJsonParser) {.inline.} = - ## closes the parser `my` and its associated input stream. +proc close*(my: var TJsonParser) {.inline.} = + ## Closes the parser `my` and its associated input stream. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var + ## s = newStringStream("some valid json") + ## p: TJsonParser + ## p.open(s, "<input string>") + ## finally: p.close + ## # write here parsing of input lexbase.close(my) proc str*(my: TJsonParser): string {.inline.} = - ## returns the character data for the events: ``jsonInt``, ``jsonFloat``, - ## ``jsonString`` + ## Returns the character data for the `events <#TJsonEventKind>`_ + ## ``jsonInt``, ``jsonFloat`` and ``jsonString``. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds when used + ## with other event types. See `next() <#next>`_ for an usage example. assert(my.kind in {jsonInt, jsonFloat, jsonString}) return my.a proc getInt*(my: TJsonParser): BiggestInt {.inline.} = - ## returns the number for the event: ``jsonInt`` + ## Returns the number for the `jsonInt <#TJsonEventKind>`_ event. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds when used + ## with other event types. See `next() <#next>`_ for an usage example. assert(my.kind == jsonInt) return parseBiggestInt(my.a) proc getFloat*(my: TJsonParser): float {.inline.} = - ## returns the number for the event: ``jsonFloat`` + ## Returns the number for the `jsonFloat <#TJsonEventKind>`_ event. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds when used + ## with other event types. See `next() <#next>`_ for an usage example. assert(my.kind == jsonFloat) return parseFloat(my.a) proc kind*(my: TJsonParser): TJsonEventKind {.inline.} = - ## returns the current event type for the JSON parser + ## Returns the current event type for the `JSON parser <#TJsonParser>`_. + ## + ## Call this proc just after `next() <#next>`_ to act on the new event. return my.kind proc getColumn*(my: TJsonParser): int {.inline.} = - ## get the current column the parser has arrived at. + ## Get the current column the parser has arrived at. + ## + ## While this is mostly used by procs like `errorMsg() <#errorMsg>`_ you can + ## use it as well to show user warnings if you are validating JSON values + ## during parsing. See `next() <#next>`_ for the full example: + ## + ## .. code-block:: nimrod + ## case parser.kind + ## ... + ## of jsonString: + ## let inputValue = parser.str + ## if previousValues.contains(inputValue): + ## echo "$1($2, $3) Warning: repeated value '$4'" % [ + ## parser.getFilename, $parser.getLine, $parser.getColumn, + ## inputValue] + ## ... result = getColNumber(my, my.bufpos) proc getLine*(my: TJsonParser): int {.inline.} = - ## get the current line the parser has arrived at. + ## Get the current line the parser has arrived at. + ## + ## While this is mostly used by procs like `errorMsg() <#errorMsg>`_ you can + ## use it as well to indicate user warnings if you are validating JSON values + ## during parsing. See `next() <#next>`_ and `getColumn() <#getColumn>`_ for + ## examples. result = my.lineNumber proc getFilename*(my: TJsonParser): string {.inline.} = - ## get the filename of the file that the parser processes. + ## Get the filename of the file that the parser is processing. + ## + ## This is the value you pass to the `open() <#open>`_ proc. While this is + ## mostly used by procs like `errorMsg() <#errorMsg>`_ you can use it as well + ## to indicate user warnings if you are validating JSON values during + ## parsing. See `next() <#next>`_ and `getColumn() <#getColumn>`_ for + ## examples. result = my.filename proc errorMsg*(my: TJsonParser): string = - ## returns a helpful error message for the event ``jsonError`` + ## Returns a helpful error message for the `jsonError <#TJsonEventKind>`_ + ## event. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds when used + ## with other event types. See `next() <#next>`_ for an usage example. assert(my.kind == jsonError) result = "$1($2, $3) Error: $4" % [ my.filename, $getLine(my), $getColumn(my), errorMessages[my.err]] proc errorMsgExpected*(my: TJsonParser, e: string): string = - ## returns an error message "`e` expected" in the same format as the - ## other error messages + ## Returns an error message "`e` expected". + ## + ## The message is in the same format as the other error messages which + ## include the parser filename, line and column values. This is used by + ## `raiseParseErr() <#raiseParseErr>`_ to raise an `EJsonParsingError + ## <#EJsonParsingError>`_. result = "$1($2, $3) Error: $4" % [ my.filename, $getLine(my), $getColumn(my), e & " expected"] @@ -382,7 +478,32 @@ proc getTok(my: var TJsonParser): TTokKind = my.tok = result proc next*(my: var TJsonParser) = - ## retrieves the first/next event. This controls the parser. + ## Retrieves the first/next event for the `JSON parser <#TJsonParser>`_. + ## + ## You are meant to call this method inside an infinite loop. After each + ## call, check the result of the `kind() <#kind>`_ proc to know what has to + ## be done next (eg. break out due to end of file). Here is a basic example + ## which simply echoes all found elements by the parser: + ## + ## .. code-block:: nimrod + ## parser.open(stream, "<input string>") + ## while true: + ## parser.next + ## case parser.kind + ## of jsonError: + ## echo parser.errorMsg + ## break + ## of jsonEof: break + ## of jsonString: echo parser.str + ## of jsonInt: echo parser.getInt + ## of jsonFloat: echo parser.getFloat + ## of jsonTrue: echo "true" + ## of jsonFalse: echo "false" + ## of jsonNull: echo "null" + ## of jsonObjectStart: echo "{" + ## of jsonObjectEnd: echo "}" + ## of jsonArrayStart: echo "[" + ## of jsonArrayEnd: echo "]" var tk = getTok(my) var i = my.state.len-1 # the following code is a state machine. If we had proper coroutines, @@ -502,7 +623,16 @@ proc next*(my: var TJsonParser) = # ------------- higher level interface --------------------------------------- type - TJsonNodeKind* = enum ## possible JSON node types + TJsonNodeKind* = enum ## Possible `JSON node <#TJsonNodeKind>`_ types. \ + ## + ## To build nodes use the helper procs + ## `newJNull() <#newJNull>`_, + ## `newJBool() <#newJBool>`_, + ## `newJInt() <#newJInt>`_, + ## `newJFloat() <#newJFloat>`_, + ## `newJString() <#newJString>`_, + ## `newJObject() <#newJObject>`_ and + ## `newJArray() <#newJArray>`_. JNull, JBool, JInt, @@ -511,8 +641,9 @@ type JObject, JArray - PJsonNode* = ref TJsonNode ## JSON node - TJsonNode* {.final, pure, acyclic.} = object + PJsonNode* = ref TJsonNode ## Reference to a `JSON node <#TJsonNode>`_. + TJsonNode* {.final, pure, acyclic.} = object ## `Object variant \ + ## <manual.html#object-variants>`_ wrapping all possible JSON types. case kind*: TJsonNodeKind of JString: str*: string @@ -529,14 +660,36 @@ type of JArray: elems*: seq[PJsonNode] - EJsonParsingError* = object of EInvalidValue ## is raised for a JSON error + EJsonParsingError* = object of EInvalidValue ## Raised during JSON parsing. \ + ## + ## Example: + ## + ## .. code-block:: nimrod + ## let smallJson = """{"test: 1.3, "key2": true}""" + ## try: + ## discard parseJson(smallJson) + ## # --> Bad JSON! input(1, 18) Error: : expected + ## except EJsonParsingError: + ## echo "Bad JSON! " & getCurrentExceptionMsg() proc raiseParseErr*(p: TJsonParser, msg: string) {.noinline, noreturn.} = - ## raises an `EJsonParsingError` exception. + ## Raises an `EJsonParsingError <#EJsonParsingError>`_ exception. + ## + ## The message for the exception will be built passing the `msg` parameter to + ## the `errorMsgExpected() <#errorMsgExpected>`_ proc. raise newException(EJsonParsingError, errorMsgExpected(p, msg)) proc newJString*(s: string): PJsonNode = - ## Creates a new `JString PJsonNode`. + ## Creates a new `JString PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJString("A string") + ## echo node + ## # --> "A string" + ## + ## Or you can use the shorter `%() proc <#%,string>`_. new(result) result.kind = JString result.str = s @@ -547,80 +700,206 @@ proc newJStringMove(s: string): PJsonNode = shallowCopy(result.str, s) proc newJInt*(n: BiggestInt): PJsonNode = - ## Creates a new `JInt PJsonNode`. + ## Creates a new `JInt PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJInt(900_100_200_300) + ## echo node + ## # --> 900100200300 + ## + ## Or you can use the shorter `%() proc <#%,BiggestInt>`_. new(result) result.kind = JInt result.num = n proc newJFloat*(n: float): PJsonNode = - ## Creates a new `JFloat PJsonNode`. + ## Creates a new `JFloat PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJFloat(3.14) + ## echo node + ## # --> 3.14 + ## + ## Or you can use the shorter `%() proc <#%,float>`_. new(result) result.kind = JFloat result.fnum = n proc newJBool*(b: bool): PJsonNode = - ## Creates a new `JBool PJsonNode`. + ## Creates a new `JBool PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJBool(true) + ## echo node + ## # --> true + ## + ## Or you can use the shorter `%() proc <#%,bool>`_. new(result) result.kind = JBool result.bval = b proc newJNull*(): PJsonNode = - ## Creates a new `JNull PJsonNode`. + ## Creates a new `JNull PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = newJNull() + ## echo node + ## # --> null new(result) proc newJObject*(): PJsonNode = - ## Creates a new `JObject PJsonNode` + ## Creates a new `JObject PJsonNode <#TJsonNodeKind>`_. + ## + ## The `PJsonNode <#PJsonNode>`_ will be initialized with an empty ``fields`` + ## sequence to which you can add new elements. Example: + ## + ## .. code-block:: nimrod + ## var node = newJObject() + ## node.add("age", newJInt(24)) + ## node.add("name", newJString("Minah")) + ## echo node + ## # --> { "age": 24, "name": "Minah"} + ## + ## Or you can use the shorter `%() proc + ## <#%,openArray[tuple[string,PJsonNode]]>`_. new(result) result.kind = JObject result.fields = @[] proc newJArray*(): PJsonNode = - ## Creates a new `JArray PJsonNode` + ## Creates a new `JArray PJsonNode <#TJsonNodeKind>`_. + ## + ## The `PJsonNode <#PJsonNode>`_ will be initialized with an empty ``elems`` + ## sequence to which you can add new elements. Example: + ## + ## .. code-block:: nimrod + ## var node = newJArray() + ## node.add(newJString("Mixing types")) + ## node.add(newJInt(42)) + ## node.add(newJString("is madness")) + ## node.add(newJFloat(3.14)) + ## echo node + ## # --> [ "Mixing types", 42, "is madness", 3.14] + ## + ## Or you can use the shorter `%() proc <#%,openArray[PJsonNode]>`_. new(result) result.kind = JArray result.elems = @[] proc `%`*(s: string): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JString PJsonNode`. + ## Creates a new `JString PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %"A string" + ## echo node + ## # --> "A string" + ## + ## This generic constructor is equivalent to the `newJString() + ## <#newJString>`_ proc. new(result) result.kind = JString result.str = s proc `%`*(n: BiggestInt): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JInt PJsonNode`. + ## Creates a new `JInt PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %900_100_200_300 + ## echo node + ## # --> 900100200300 + ## + ## This generic constructor is equivalent to the `newJInt() <#newJInt>`_ + ## proc. new(result) result.kind = JInt result.num = n proc `%`*(n: float): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JFloat PJsonNode`. + ## Creates a new `JFloat PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %3.14 + ## echo node + ## # --> 3.14 + ## + ## This generic constructor is equivalent to the `newJFloat() <#newJFloat>`_ + ## proc. new(result) result.kind = JFloat result.fnum = n proc `%`*(b: bool): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JBool PJsonNode`. + ## Creates a new `JBool PJsonNode <#TJsonNodeKind>`_. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %true + ## echo node + ## # --> true + ## + ## This generic constructor is equivalent to the `newJBool() <#newJBool>`_ + ## proc. new(result) result.kind = JBool result.bval = b proc `%`*(keyVals: openArray[tuple[key: string, val: PJsonNode]]): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JObject PJsonNode` + ## Creates a new `JObject PJsonNode <#TJsonNodeKind>`_. + ## + ## Unlike the `newJObject() <#newJObject>`_ proc, which returns an object + ## that has to be further manipulated, you can use this generic constructor + ## to create JSON objects with all their fields in one go. Example: + ## + ## .. code-block:: nimrod + ## let node = %[("age", %24), ("name", %"Minah")] + ## echo node + ## # --> { "age": 24, "name": "Minah"} new(result) result.kind = JObject newSeq(result.fields, keyVals.len) for i, p in pairs(keyVals): result.fields[i] = p proc `%`*(elements: openArray[PJsonNode]): PJsonNode = - ## Generic constructor for JSON data. Creates a new `JArray PJsonNode` + ## Creates a new `JArray PJsonNode <#TJsonNodeKind>`_. + ## + ## Unlike the `newJArray() <#newJArray>`_ proc, which returns an object + ## that has to be further manipulated, you can use this generic constructor + ## to create JSON arrays with all their values in one go. Example: + ## + ## .. code-block:: nimrod + ## let node = %[%"Mixing types", %42, + ## %"is madness", %3.14,] + ## echo node + ## # --> [ "Mixing types", 42, "is madness", 3.14] new(result) result.kind = JArray newSeq(result.elems, elements.len) for i, p in pairs(elements): result.elems[i] = p proc `==`* (a,b: PJsonNode): bool = - ## Check two nodes for equality + ## Check two `PJsonNode <#PJsonNode>`_ nodes for equality. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## assert(%1 == %1) + ## assert(%1 != %2) if a.isNil: if b.isNil: return true return false @@ -644,7 +923,21 @@ proc `==`* (a,b: PJsonNode): bool = a.fields == b.fields proc hash* (n:PJsonNode): THash = - ## Compute the hash for a JSON node + ## Computes the hash for a JSON node. + ## + ## The `THash <hashes.html#THash>`_ allows JSON nodes to be used as keys for + ## `sets <sets.html>`_ or `tables <tables.html>`_. Example: + ## + ## .. code-block:: nimrod + ## import json, sets + ## + ## var + ## uniqueValues = initSet[PJsonNode]() + ## values = %[%1, %2, %1, %2, %3] + ## for value in values.elems: + ## discard uniqueValues.containsOrIncl(value) + ## echo uniqueValues + ## # --> {1, 2, 3} case n.kind of JArray: result = hash(n.elems) @@ -662,17 +955,40 @@ proc hash* (n:PJsonNode): THash = result = hash(0) proc len*(n: PJsonNode): int = - ## If `n` is a `JArray`, it returns the number of elements. - ## If `n` is a `JObject`, it returns the number of pairs. - ## Else it returns 0. + ## Returns the number of children items for this `PJsonNode <#PJsonNode>`_. + ## + ## If `n` is a `JArray <#TJsonNodeKind>`_, it will return the number of + ## elements. If `n` is a `JObject <#TJsonNodeKind>`_, it will return the + ## number of key-value pairs. For all other types this proc returns zero. + ## Example: + ## + ## .. code-block:: nimrod + ## let + ## n1 = %[("age", %33), ("name", %"Sojin")] + ## n2 = %[%1, %2, %3, %4, %5, %6, %7] + ## n3 = %"Some odd string we have here" + ## echo n1.len # --> 2 + ## echo n2.len # --> 7 + ## echo n3.len # --> 0 + ## case n.kind of JArray: result = n.elems.len of JObject: result = n.fields.len else: discard 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 + ## Gets a named field from a `JObject <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_. + ## + ## Returns the value for `name` or nil if `node` doesn't contain such a + ## field. This proc will `assert <system.html#assert>`_ in debug builds if + ## `name` is ``nil`` or `node` is not a ``JObject``. On release builds it + ## will likely crash. Example: + ## + ## .. code-block:: nimrod + ## let node = %[("age", %40), ("name", %"Britney")] + ## echo node["name"] + ## # --> "Britney" assert(not isNil(node)) assert(node.kind == JObject) for key, item in items(node.fields): @@ -681,35 +997,92 @@ proc `[]`*(node: PJsonNode, name: string): PJsonNode = return nil proc `[]`*(node: PJsonNode, index: int): PJsonNode = - ## Gets the node at `index` in an Array. Result is undefined if `index` - ## is out of bounds + ## Gets the `index` item from a `JArray <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_. + ## + ## Returns the specified item. Result is undefined if `index` is out of + ## bounds. This proc will `assert <system.html#assert>`_ in debug builds if + ## `node` is ``nil`` or not a ``JArray``. Example: + ## + ## .. code-block:: nimrod + ## let node = %[%"Mixing types", %42, + ## %"is madness", %3.14,] + ## echo node[2] + ## # --> "is madness" assert(not isNil(node)) assert(node.kind == JArray) return node.elems[index] proc hasKey*(node: PJsonNode, key: string): bool = - ## Checks if `key` exists in `node`. + ## Returns `true` if `key` exists in a `JObject <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds if `node` is + ## not a ``JObject``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## let node = %[("age", %40), ("name", %"Britney")] + ## echo node.hasKey("email") + ## # --> false assert(node.kind == JObject) for k, item in items(node.fields): if k == key: return true proc existsKey*(node: PJsonNode, key: string): bool {.deprecated.} = node.hasKey(key) - ## Deprecated for `hasKey` + ## Deprecated for `hasKey() <#hasKey>`_. proc add*(father, child: PJsonNode) = - ## Adds `child` to a JArray node `father`. + ## Adds `child` to a `JArray <#TJsonNodeKind>`_ `PJsonNode <#PJsonNode>`_ + ## `father` node. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds if `node` is + ## not a ``JArray``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## var node = %[%"Mixing types", %42] + ## node.add(%"is madness") + ## echo node + ## # --> false assert father.kind == JArray father.elems.add(child) proc add*(obj: PJsonNode, key: string, val: PJsonNode) = - ## Adds ``(key, val)`` pair to the JObject node `obj`. For speed - ## reasons no check for duplicate keys is performed! - ## But ``[]=`` performs the check. + ## Adds ``(key, val)`` pair to a `JObject <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_ `obj` node. + ## + ## For speed reasons no check for duplicate keys is performed! But ``[]=`` + ## performs the check. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds if `node` is + ## not a ``JObject``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## var node = newJObject() + ## node.add("age", newJInt(12)) + ## # This is wrong! But we need speed… + ## node.add("age", newJInt(24)) + ## echo node + ## # --> { "age": 12, "age": 24} assert obj.kind == JObject obj.fields.add((key, val)) proc `[]=`*(obj: PJsonNode, key: string, val: PJsonNode) = - ## Sets a field from a `JObject`. Performs a check for duplicate keys. + ## Sets a field from a `JObject <#TJsonNodeKind>`_ `PJsonNode + ## <#PJsonNode>`_ `obj` node. + ## + ## Unlike the `add() <#add,PJsonNode,string,PJsonNode>`_ proc this will + ## perform a check for duplicate keys and replace existing values. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds if `node` is + ## not a ``JObject``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## var node = newJObject() + ## node["age"] = %12 + ## # The new value replaces the previous one. + ## node["age"] = %24 + ## echo node + ## # --> { "age": 24} assert(obj.kind == JObject) for i in 0..obj.fields.len-1: if obj.fields[i].key == key: @@ -736,6 +1109,18 @@ proc `{}=`*(node: PJsonNode, names: varargs[string], value: PJsonNode) = proc delete*(obj: PJsonNode, key: string) = ## Deletes ``obj[key]`` preserving the order of the other (key, value)-pairs. + ## + ## If `key` doesn't exist in `obj` ``EInvalidIndex`` will be raised. This + ## proc will `assert <system.html#assert>`_ in debug builds if `node` is not + ## a ``JObject``. On release builds it will likely crash. Example: + ## + ## .. code-block:: nimrod + ## var node = %[("age", %37), ("name", %"Chris"), ("male", %false)] + ## echo node + ## # --> { "age": 37, "name": "Chris", "male": false} + ## node.delete("age") + ## echo node + ## # --> { "name": "Chris", "male": false} assert(obj.kind == JObject) for i in 0..obj.fields.len-1: if obj.fields[i].key == key: @@ -744,7 +1129,9 @@ proc delete*(obj: PJsonNode, key: string) = raise newException(EInvalidIndex, "key not in object") proc copy*(p: PJsonNode): PJsonNode = - ## Performs a deep copy of `a`. + ## Performs a deep copy of `p`. + ## + ## Modifications to the copy won't affect the original. case p.kind of JString: result = newJString(p.str) @@ -779,6 +1166,12 @@ proc nl(s: var string, ml: bool) = proc escapeJson*(s: string): string = ## Converts a string `s` to its JSON representation. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## echo """name: "Torbjørn"""".escapeJson + ## # --> "name: \"Torbj\u00F8rn\"" result = newStringOfCap(s.len + s.len shr 3) result.add("\"") for x in runes(s): @@ -850,24 +1243,58 @@ proc toPretty(result: var string, node: PJsonNode, indent = 2, ml = true, result.add("null") proc pretty*(node: PJsonNode, indent = 2): string = - ## Converts `node` to its JSON Representation, with indentation and - ## on multiple lines. + ## Converts `node` to a pretty JSON representation. + ## + ## The representation will have indentation use multiple lines. Example: + ## + ## .. code-block:: nimrod + ## let node = %[("age", %33), ("name", %"Sojin")] + ## echo node + ## # --> { "age": 33, "name": "Sojin"} + ## echo node.pretty + ## # --> { + ## # "age": 33, + ## # "name": "Sojin" + ## # } result = "" toPretty(result, node, indent) proc `$`*(node: PJsonNode): string = - ## Converts `node` to its JSON Representation on one line. + ## Converts `node` to its JSON representation on one line. result = "" toPretty(result, node, 1, false) iterator items*(node: PJsonNode): PJsonNode = - ## Iterator for the items of `node`. `node` has to be a JArray. + ## Iterator for the items of `node`. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds if `node` is + ## not a `JArray <#TJsonNodeKind>`_. On release builds it will likely crash. + ## Example: + ## + ## .. code-block:: nimrod + ## let numbers = %[%1, %2, %3] + ## for n in numbers.items: + ## echo "Number ", n + ## ## --> Number 1 + ## ## Number 2 + ## ## Number 3 assert node.kind == JArray for i in items(node.elems): yield i iterator pairs*(node: PJsonNode): tuple[key: string, val: PJsonNode] = - ## Iterator for the child elements of `node`. `node` has to be a JObject. + ## Iterator for the child elements of `node`. + ## + ## This proc will `assert <system.html#assert>`_ in debug builds if `node` is + ## not a `JObject <#TJsonNodeKind>`_. On release builds it will likely crash. + ## Example: + ## + ## .. code-block:: nimrod + ## var node = %[("age", %37), ("name", %"Chris")] + ## for key, value in node.pairs: + ## echo "Key: ", key, ", value: ", value + ## # --> Key: age, value: 37 + ## # Key: name, value: "Chris" assert node.kind == JObject for key, val in items(node.fields): yield (key, val) @@ -926,8 +1353,12 @@ proc parseJson(p: var TJsonParser): PJsonNode = when not defined(js): proc parseJson*(s: PStream, filename: string): PJsonNode = - ## Parses from a stream `s` into a `PJsonNode`. `filename` is only needed - ## for nice error messages. + ## Generic convenience proc to parse stream `s` into a `PJsonNode`. + ## + ## This wraps around `open() <#open>`_ and `next() <#next>`_ to return the + ## full JSON DOM. Errors will be raised as exceptions, this requires the + ## `filename` parameter to not be ``nil`` to avoid crashes. + assert(not isNil(filename)) var p: TJsonParser p.open(s, filename) discard getTok(p) # read first token @@ -936,10 +1367,28 @@ when not defined(js): proc parseJson*(buffer: string): PJsonNode = ## Parses JSON from `buffer`. + ## + ## Specialized version around `parseJson(PStream, string) + ## <#parseJson,PStream,string>`_. Example: + ## + ## .. code-block:: nimrod + ## let + ## smallJson = """{"test": 1.3, "key2": true}""" + ## jobj = parseJson(smallJson) + ## assert jobj.kind == JObject + ## + ## assert jobj["test"].kind == JFloat + ## echo jobj["test"].fnum # --> 1.3 + ## + ## assert jobj["key2"].kind == JBool + ## echo jobj["key2"].bval # --> true result = parseJson(newStringStream(buffer), "input") proc parseFile*(filename: string): PJsonNode = ## Parses `file` into a `PJsonNode`. + ## + ## Specialized version around `parseJson(PStream, string) + ## <#parseJson,PStream,string>`_. var stream = newFileStream(filename, fmRead) if stream == nil: raise newException(EIO, "cannot read from file: " & filename) |