diff options
author | Araq <rumpf_a@web.de> | 2011-01-15 11:50:12 +0100 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2011-01-15 11:50:12 +0100 |
commit | ff0b0f6b6dc433ae34dff37f5c78f2661c153d10 (patch) | |
tree | ddc9fde5848515659362d3e5380a897299bd4a27 /lib/pure/json.nim | |
parent | faac1bee85f3fd5b3b2d3fb407e5d98adef3db21 (diff) | |
download | Nim-ff0b0f6b6dc433ae34dff37f5c78f2661c153d10.tar.gz |
json module changes
Diffstat (limited to 'lib/pure/json.nim')
-rwxr-xr-x[-rw-r--r--] | lib/pure/json.nim | 839 |
1 files changed, 670 insertions, 169 deletions
diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 0e5d2095b..c32a82397 100644..100755 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -1,25 +1,489 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2010 Andreas Rumpf, Dominik Picheta +# (c) Copyright 2011 Andreas Rumpf, Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. # -import parsejson, streams, strutils +## 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. + +import + hashes, strutils, lexbase, streams, unicode + +type + TJsonEventKind* = enum ## enumation of all events that may occur when parsing + jsonError, ## an error ocurred during parsing + jsonEof, ## end of file reached + jsonString, ## a string literal + jsonNumber, ## a number 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, + tkEof, + tkString, + tkNumber, + tkTrue, + tkFalse, + tkNull, + tkCurlyLe, + tkCurlyRi, + tkBracketLe, + tkBracketRi, + tkColon, + tkComma + + TJsonError* = enum ## enumeration that lists all errors that can occur + errNone, ## no error + errInvalidToken, ## invalid token + errStringExpected, ## string expected + errColonExpected, ## ``:`` expected + errCommaExpected, ## ``,`` expected + errBracketRiExpected, ## ``]`` expected + errCurlyRiExpected, ## ``}`` expected + errQuoteExpected, ## ``"`` or ``'`` expected + errEOC_Expected, ## ``*/`` expected + errEofExpected, ## EOF expected + errExprExpected ## expr expected + + TParserState = enum + stateEof, stateStart, stateObject, stateArray, stateExpectArrayComma, + stateExpectObjectComma, stateExpectColon, stateExpectValue + + TJsonParser* = object of TBaseLexer ## the parser object. + a: string + tok: TTokKind + kind: TJsonEventKind + err: TJsonError + state: seq[TParserState] + filename: string + +const + errorMessages: array [TJsonError, string] = [ + "no error", + "invalid token", + "string expected", + "':' expected", + "',' expected", + "']' expected", + "'}' expected", + "'\"' or \"'\" expected", + "'*/' expected", + "EOF expected", + "expression expected" + ] + tokToStr: array [TTokKind, string] = [ + "invalid token", + "EOF", + "string literal", + "number literal", + "true", + "false", + "null", + "{", "}", "[", "]", ":", "," + ] + +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) + 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. + lexbase.close(my) + +proc str*(my: TJsonParser): string {.inline.} = + ## returns the character data for the events: ``jsonNumber``, + ## ``jsonString`` + assert(my.kind in {jsonNumber, jsonString}) + return my.a + +proc number*(my: TJsonParser): float {.inline.} = + ## returns the number for the event: ``jsonNumber`` + assert(my.kind == jsonNumber) + return parseFloat(my.a) + +proc kind*(my: TJsonParser): TJsonEventKind {.inline.} = + ## returns the current event type for the JSON parser + return my.kind + +proc getColumn*(my: TJsonParser): int {.inline.} = + ## get the current column the parser has arrived at. + result = getColNumber(my, my.bufPos) + +proc getLine*(my: TJsonParser): int {.inline.} = + ## get the current line the parser has arrived at. + result = my.linenumber + +proc getFilename*(my: TJsonParser): string {.inline.} = + ## get the filename of the file that the parser processes. + result = my.filename + +proc errorMsg*(my: TJsonParser): string = + ## returns a helpful error message for the event ``jsonError`` + 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 + result = "$1($2, $3) Error: $4" % [ + my.filename, $getLine(my), $getColumn(my), e & " expected"] + +proc handleHexChar(c: Char, x: var int): bool = + result = true # Success + case c + of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) + of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) + of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) + else: result = false # error + +proc parseString(my: var TJsonParser): TTokKind = + result = tkString + var pos = my.bufpos + 1 + var buf = my.buf + while true: + case buf[pos] + of '\0': + my.err = errQuoteExpected + result = tkError + break + of '"': + inc(pos) + break + of '\\': + case buf[pos+1] + of '\\', '"', '\'', '/': + add(my.a, buf[pos+1]) + inc(pos, 2) + of 'b': + add(my.a, '\b') + inc(pos, 2) + of 'f': + add(my.a, '\f') + inc(pos, 2) + of 'n': + add(my.a, '\L') + inc(pos, 2) + of 'r': + add(my.a, '\C') + inc(pos, 2) + of 't': + add(my.a, '\t') + inc(pos, 2) + of 'u': + inc(pos, 2) + var r: int + if handleHexChar(buf[pos], r): inc(pos) + if handleHexChar(buf[pos], r): inc(pos) + if handleHexChar(buf[pos], r): inc(pos) + if handleHexChar(buf[pos], r): inc(pos) + add(my.a, toUTF8(TRune(r))) + else: + # don't bother with the error + add(my.a, buf[pos]) + inc(pos) + of '\c': + pos = lexbase.HandleCR(my, pos) + buf = my.buf + add(my.a, '\c') + of '\L': + pos = lexbase.HandleLF(my, pos) + buf = my.buf + add(my.a, '\L') + else: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos # store back + +proc skip(my: var TJsonParser) = + var pos = my.bufpos + var buf = my.buf + while true: + case buf[pos] + of '/': + if buf[pos+1] == '/': + # skip line comment: + inc(pos, 2) + while true: + case buf[pos] + of '\0': + break + of '\c': + pos = lexbase.HandleCR(my, pos) + buf = my.buf + break + of '\L': + pos = lexbase.HandleLF(my, pos) + buf = my.buf + break + else: + inc(pos) + elif buf[pos+1] == '*': + # skip long comment: + inc(pos, 2) + while true: + case buf[pos] + of '\0': + my.err = errEOC_Expected + break + of '\c': + pos = lexbase.HandleCR(my, pos) + buf = my.buf + of '\L': + pos = lexbase.HandleLF(my, pos) + buf = my.buf + of '*': + inc(pos) + if buf[pos] == '/': + inc(pos) + break + else: + inc(pos) + else: + break + of ' ', '\t': + Inc(pos) + of '\c': + pos = lexbase.HandleCR(my, pos) + buf = my.buf + of '\L': + pos = lexbase.HandleLF(my, pos) + buf = my.buf + else: + break + my.bufpos = pos + +proc parseNumber(my: var TJsonParser) = + var pos = my.bufpos + var buf = my.buf + if buf[pos] == '-': + add(my.a, '-') + inc(pos) + if buf[pos] == '.': + add(my.a, "0.") + inc(pos) + else: + while buf[pos] in Digits: + add(my.a, buf[pos]) + inc(pos) + if buf[pos] == '.': + add(my.a, '.') + inc(pos) + # digits after the dot: + while buf[pos] in Digits: + add(my.a, buf[pos]) + inc(pos) + if buf[pos] in {'E', 'e'}: + add(my.a, buf[pos]) + inc(pos) + if buf[pos] in {'+', '-'}: + add(my.a, buf[pos]) + inc(pos) + while buf[pos] in Digits: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos + +proc parseName(my: var TJsonParser) = + var pos = my.bufpos + var buf = my.buf + if buf[pos] in IdentStartChars: + while buf[pos] in IdentChars: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos + +proc getTok(my: var TJsonParser): TTokKind = + setLen(my.a, 0) + skip(my) # skip whitespace, comments + case my.buf[my.bufpos] + of '-', '.', '0'..'9': + parseNumber(my) + result = tkNumber + of '"': + result = parseString(my) + of '[': + inc(my.bufpos) + result = tkBracketLe + of '{': + inc(my.bufpos) + result = tkCurlyLe + of ']': + inc(my.bufpos) + result = tkBracketRi + of '}': + inc(my.bufpos) + result = tkCurlyRi + of ',': + inc(my.bufpos) + result = tkComma + of ':': + inc(my.bufpos) + result = tkColon + of '\0': + result = tkEof + of 'a'..'z', 'A'..'Z', '_': + parseName(my) + case my.a + of "null": result = tkNull + of "true": result = tkTrue + of "false": result = tkFalse + else: result = tkError + else: + inc(my.bufpos) + result = tkError + my.tok = result + +proc next*(my: var TJsonParser) = + ## retrieves the first/next event. This controls the parser. + var tk = getTok(my) + var i = my.state.len-1 + # the following code is a state machine. If we had proper coroutines, + # the code could be much simpler. + case my.state[i] + of stateEof: + if tk == tkEof: + my.kind = jsonEof + else: + my.kind = jsonError + my.err = errEofExpected + of stateStart: + # tokens allowed? + case tk + of tkString, tkNumber, tkTrue, tkFalse, tkNull: + my.state[i] = stateEof # expect EOF next! + my.kind = TJsonEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateArray) # we expect any + my.kind = jsonArrayStart + of tkCurlyLe: + my.state.add(stateObject) + my.kind = jsonObjectStart + of tkEof: + my.kind = jsonEof + else: + my.kind = jsonError + my.err = errEofExpected + of stateObject: + case tk + of tkString, tkNumber, tkTrue, tkFalse, tkNull: + my.state.add(stateExpectColon) + my.kind = TJsonEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateExpectColon) + my.state.add(stateArray) + my.kind = jsonArrayStart + of tkCurlyLe: + my.state.add(stateExpectColon) + my.state.add(stateObject) + my.kind = jsonObjectStart + of tkCurlyRi: + my.kind = jsonObjectEnd + discard my.state.pop() + else: + my.kind = jsonError + my.err = errCurlyRiExpected + of stateArray: + case tk + of tkString, tkNumber, tkTrue, tkFalse, tkNull: + my.state.add(stateExpectArrayComma) # expect value next! + my.kind = TJsonEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateExpectArrayComma) + my.state.add(stateArray) + my.kind = jsonArrayStart + of tkCurlyLe: + my.state.add(stateExpectArrayComma) + my.state.add(stateObject) + my.kind = jsonObjectStart + of tkBracketRi: + my.kind = jsonArrayEnd + discard my.state.pop() + else: + my.kind = jsonError + my.err = errBracketRiExpected + of stateExpectArrayComma: + case tk + of tkComma: + discard my.state.pop() + next(my) + of tkBracketRi: + my.kind = jsonArrayEnd + discard my.state.pop() # pop stateExpectArrayComma + discard my.state.pop() # pop stateArray + else: + my.kind = jsonError + my.err = errBracketRiExpected + of stateExpectObjectComma: + case tk + of tkComma: + discard my.state.pop() + next(my) + of tkCurlyRi: + my.kind = jsonObjectEnd + discard my.state.pop() # pop stateExpectObjectComma + discard my.state.pop() # pop stateObject + else: + my.kind = jsonError + my.err = errCurlyRiExpected + of stateExpectColon: + case tk + of tkColon: + my.state[i] = stateExpectValue + next(my) + else: + my.kind = jsonError + my.err = errColonExpected + of stateExpectValue: + case tk + of tkString, tkNumber, tkTrue, tkFalse, tkNull: + my.state[i] = stateExpectObjectComma + my.kind = TJsonEventKind(ord(tk)) + of tkBracketLe: + my.state[i] = stateExpectObjectComma + my.state.add(stateArray) + my.kind = jsonArrayStart + of tkCurlyLe: + my.state[i] = stateExpectObjectComma + my.state.add(stateObject) + my.kind = jsonObjectStart + else: + my.kind = jsonError + my.err = errExprExpected + + +# ------------- higher level interface --------------------------------------- type - TJsonNodeKind* = enum - JString, - JNumber, - JBool, + TJsonNodeKind* = enum ## possible JSON node types JNull, + JBool, + JNumber, + JString, JObject, JArray - PJsonNode* = ref TJsonNode - TJsonNode* = object + PJsonNode* = ref TJsonNode ## JSON node + TJsonNode {.final, pure.} = object case kind*: TJsonNodeKind of JString: str*: String @@ -30,18 +494,99 @@ type of JNull: nil of JObject: - fields*: seq[tuple[key: string, obj: PJsonNode]] + fields*: seq[tuple[key: string, val: PJsonNode]] of JArray: elems*: seq[PJsonNode] EJsonParsingError* = object of EBase -proc raiseParseErr(parser: TJsonParser, msg: string, line = True) = - if line: - raise newException(EJsonParsingError, "(" & $parser.getLine & ", " & - $parser.getColumn & ") " & msg) - else: - raise newException(EJsonParsingError, msg) +proc raiseParseErr(p: TJsonParser, msg: string) = + raise newException(EJsonParsingError, errorMsgExpected(p, msg)) + +proc newJString*(s: String): PJsonNode = + ## Creates a new `JString PJsonNode`. + new(result) + result.kind = JString + result.str = s + +proc newJNumber*(n: Float): PJsonNode = + ## Creates a new `JNumber PJsonNode`. + new(result) + result.kind = JNumber + result.num = n + +proc newJBool*(b: Bool): PJsonNode = + ## Creates a new `JBool PJsonNode`. + new(result) + result.kind = JBool + result.bval = b + +proc newJNull*(): PJsonNode = + ## Creates a new `JNull PJsonNode`. + new(result) + +proc newJObject*(): PJsonNode = + ## Creates a new `JObject PJsonNode` + new(result) + result.kind = JObject + result.fields = @[] + +proc newJArray*(): PJsonNode = + ## Creates a new `JArray PJsonNode` + new(result) + result.kind = JArray + result.elems = @[] + +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. + case n.kind + of JArray: result = n.elems.len + of JObject: result = n.fields.len + else: nil + +proc `[]`*(node: PJsonNode, name: String): PJsonNode = + ## Gets a field from a `JObject`. + assert(node.kind == JObject) + for key, item in items(node.fields): + if key == name: + return item + return nil + +proc `[]`*(node: PJsonNode, index: Int): PJsonNode = + ## Gets the node at `index` in an Array. + assert(node.kind == JArray) + return node.elems[index] + +proc existsKey*(node: PJsonNode, 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 + +proc add*(father, child: PJsonNode) = + ## Adds `child` to a JArray node `father`. + 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. + 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. + 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)) + +# ------------- pretty printing ---------------------------------------------- proc indent(s: var string, i: int) = s.add(repeatChar(i)) @@ -53,7 +598,24 @@ proc newIndent(curr, indent: int, ml: bool): Int = proc nl(s: var string, ml: bool) = if ml: s.add("\n") -proc toPretty(result: var string, node: PJsonNode, indent = 2, ml = True, lstArr = False, currIndent = 0) = +proc escapeJson*(s: string): string = + ## Converts a string `s` to its JSON representation. + result = "\"" + for x in runes(s): + var r = int(x) + if r >= 32 and r <= 127: + var c = chr(r) + case c + of '"': result.add("\\\"") + of '\\': result.add("\\\\") + else: result.add(c) + else: + result.add("\\u") + result.add(toHex(r, 4)) + result.add("\"") + +proc toPretty(result: var string, node: PJsonNode, indent = 2, ml = True, + lstArr = False, currIndent = 0) = case node.kind of JObject: if currIndent != 0 and not lstArr: result.nl(ml) @@ -64,16 +626,18 @@ proc toPretty(result: var string, node: PJsonNode, indent = 2, ml = True, lstArr if i > 0: result.add(", ") result.nl(ml) # New Line - var (key, item) = node.fields[i] - result.indent(newIndent(currIndent, indent, ml)) # Need to indent more than { - result.add("\"" & key & "\": ") - toPretty(result, item, indent, ml, False, newIndent(currIndent, indent, ml)) + # Need to indent more than { + result.indent(newIndent(currIndent, indent, ml)) + result.add(escapeJson(node.fields[i].key)) + result.add(": ") + toPretty(result, node.fields[i].val, indent, ml, False, + newIndent(currIndent, indent, ml)) result.nl(ml) result.indent(currIndent) # indent the same as { result.add("}") of JString: if lstArr: result.indent(currIndent) - result.add("\"" & node.str & "\"") + result.add(escapeJson(node.str)) of JNumber: if lstArr: result.indent(currIndent) result.add($node.num) @@ -99,167 +663,106 @@ proc toPretty(result: var string, node: PJsonNode, indent = 2, ml = True, lstArr result.add("null") proc pretty*(node: PJsonNode, indent = 2): String = - ## Converts a `PJsonNode` to its JSON Representation, with indentation and + ## Converts `node` to its JSON Representation, with indentation and ## on multiple lines. result = "" toPretty(result, node, indent) proc `$`*(node: PJsonNode): String = - ## Converts a `PJsonNode` to its JSON Representation on one line. + ## Converts `node` to its JSON Representation on one line. result = "" toPretty(result, node, 1, False) -proc newJString*(s: String): PJsonNode = - ## Creates a new `JString PJsonNode` - new(result) - result.kind = JString - result.str = s +proc eat(p: var TJsonParser, tok: TTokKind) = + if p.tok == tok: discard getTok(p) + else: raiseParseErr(p, tokToStr[tok]) -proc newJNumber*(n: Float): PJsonNode = - ## Creates a new `JNumber PJsonNode` - new(result) - result.kind = JNumber - result.num = n - -proc newJBool*(b: Bool): PJsonNode = - ## Creates a new `JBool PJsonNode` - new(result) - result.kind = JBool - result.bval = b +proc parseJson(p: var TJsonParser): PJsonNode = + ## Parses JSON from a JSON Parser `p`. + case p.tok + of tkString: + result = newJString(p.a) + discard getTok(p) + of tkNumber: + result = newJNumber(parseFloat(p.a)) + discard getTok(p) + of tkTrue: + result = newJBool(true) + discard getTok(p) + of tkFalse: + result = newJBool(false) + discard getTok(p) + of tkNull: + result = newJNull() + discard getTok(p) + of tkCurlyLe: + result = newJObject() + discard getTok(p) + while p.tok != tkCurlyRi: + if p.tok != tkString: + raiseParseErr(p, "string literal as key expected") + var key = p.a + discard getTok(p) + eat(p, tkColon) + var val = parseJson(p) + result[key] = val + if p.tok != tkComma: break + discard getTok(p) + eat(p, tkCurlyRi) + of tkBracketLe: + result = newJArray() + discard getTok(p) + while p.tok != tkBracketRi: + result.add(parseJson(p)) + if p.tok != tkComma: break + discard getTok(p) + eat(p, tkBracketRi) + of tkError, tkCurlyRi, tkBracketRi, tkColon, tkComma, tkEof: + raiseParseErr(p, "{") -proc newJNull*(): PJsonNode = - ## Creates a new `JNull PJsonNode` - new(result) - result.kind = JNull +proc parseJson*(s: PStream, filename: string): PJsonNode = + ## Parses from a stream `s` into a `PJsonNode`. `filename` is only needed + ## for nice error messages. + var p: TJsonParser + p.open(s, filename) + discard getTok(p) # read first token + result = p.parseJson() + p.close() -proc newJObject*(f: seq[tuple[key: string, obj: PJsonNode]]): PJsonNode = - ## Creates a new `JObject PJsonNode` - new(result) - result.kind = JObject - result.fields = f +proc parseJson*(buffer: string): PJsonNode = + ## Parses JSON from `buffer`. + result = parseJson(newStringStream(buffer), "input") -proc newJArray*(a: seq[PJsonNode]): PJsonNode = - ## Creates a new `JArray PJsonNode` - new(result) - result.kind = JArray - result.elems = a +proc parseFile*(filename: string): PJsonNode = + ## Parses `file` into a `PJsonNode`. + var stream = newFileStream(filename, fmRead) + if stream == nil: + raise newException(EIO, "cannot read from file: " & filename) + result = parseJson(stream, filename) -proc parseOther(parser: var TJsonParser): PJsonNode = - # Parses a *single* node which is not an Array or Object. - new(result) - case parser.kind - of jsonString: - result = newJString(parser.str()) - of jsonNumber: - result = newJNumber(parser.number()) - of jsonTrue, jsonFalse: - result = newJBool((parser.kind == jsonTrue)) - of jsonNull: - result = newJNull() - of jsonError: - parser.raiseParseErr(parser.errorMsg(), false) - else: parser.raiseParseErr("Unexpected " & $parser.kind & " here.") - -proc parseObj(parser: var TJSonParser, oStart: Bool = False): PJsonNode - -proc parseArray(parser: var TJsonParser): PJsonNode = - result = newJArray(@[]) - while True: - parser.next() - case parser.kind - of jsonArrayStart: - # Array in an array. - var arr = parser.parseArray() - result.elems.add(arr) - of jsonArrayEnd: - return - of jsonString, jsonNumber, jsonTrue, jsonFalse, jsonNull: - var other = parser.parseOther() - result.elems.add(other) - of jsonObjectStart: - var obj = parser.parseObj(True) - result.elems.add(obj) - of jsonObjectEnd: parser.raiseParseErr("Unexpected }") - of jsonEof: parser.raiseParseErr("Unexpected EOF.") - of jsonError: parser.raiseParseErr(parser.errorMsg(), false) - -proc parseObj(parser: var TJSonParser, oStart: Bool = False): PJsonNode = - var key = "" - var objStarted = oStart - result = newJObject(@[]) - while True: - parser.next() - case parser.kind +when false: + import os + var s = newFileStream(ParamStr(1), fmRead) + if s == nil: quit("cannot open the file" & ParamStr(1)) + var x: TJsonParser + open(x, s, ParamStr(1)) + while true: + next(x) + case x.kind of jsonError: - parser.raiseParseErr(parser.errorMsg(), false) + Echo(x.errorMsg()) break of jsonEof: break - of jsonString, jsonNumber, jsonTrue, jsonFalse, jsonNull: - if parser.kind == jsonString and (key == "" and objStarted): - key = parser.str() - elif key == "": - parser.raiseParseErr("Expected object or array.") - else: - var obj = parser.parseOther() - result.fields.add((key, obj)) - key = "" - of jsonObjectStart: - objStarted = True - if key != "": - # Make sure that parseObj knows that the object has been started - var obj = parser.parseObj(True) - result.fields.add((key, obj)) - key = "" - of jsonObjectEnd: return - of jsonArrayStart: - var arr = parser.parseArray() - if key != "": - result.fields.add((key, arr)) - key = "" - else: - return arr - of jsonArrayEnd: parser.raiseParseErr("Unexpected ]") - -proc parse*(json: string): PJsonNode = - ## Parses string `json` into a `PJsonNode`. - var stream = newStringStream(json) - var parser: TJsonParser - parser.open(stream, "") - result = parser.parseObj() + of jsonString, jsonNumber: echo(x.str) + of jsonTrue: Echo("!TRUE") + of jsonFalse: Echo("!FALSE") + of jsonNull: Echo("!NULL") + of jsonObjectStart: Echo("{") + of jsonObjectEnd: Echo("}") + of jsonArrayStart: Echo("[") + of jsonArrayEnd: Echo("]") - parser.close() - -proc parseFile*(file: String): PJsonNode = - ## Parses `file` into a `PJsonNode`. - var stream = newFileStream(file, fmRead) - var parser: TJsonParser - parser.open(stream, file) - result = parser.parseObj() - - parser.close() - -proc `[]`*(node: PJsonNode, name: String): PJsonNode = - ## Gets a field from a `JObject`. - assert(node.kind == JObject) - for key, item in items(node.fields): - if key == name: - return item - return nil - -proc `[]`*(node: PJsonNode, index: Int): PJsonNode = - ## Gets the node at `index` in an Array. - assert(node.kind == JArray) - return node.elems[index] - -proc existsKey*(node: PJsonNode, name: String): Bool = - ## Checks if key `name` exists in `node`. - assert(node.kind == JObject) - for key, item in items(node.fields): - if key == name: - return True - return False - - + close(x) # { "json": 5 } # To get that we shall use, obj["json"] @@ -267,12 +770,10 @@ proc existsKey*(node: PJsonNode, name: String): Bool = when isMainModule: #var node = parse("{ \"test\": null }") #echo(node.existsKey("test56")) - var parsed = parseFile("test2.json") - echo(parsed["commits"][0]["author"]["username"].str) + var parsed = parseFile("tests/testdata/jsontest.json") + echo(parsed) echo() echo(pretty(parsed, 2)) - echo() - echo(parsed) discard """ while true: |