summary refs log tree commit diff stats
path: root/lib/pure/json.nim
diff options
context:
space:
mode:
authordom96 <dominikpicheta@googlemail.com>2011-01-11 21:58:44 +0000
committerdom96 <dominikpicheta@googlemail.com>2011-01-11 21:58:44 +0000
commit49ac05ba20e017ada74d1c67944c256eefbd5d20 (patch)
treec87eadfefbd8947ed53edb1f544728f7f2e5fce7 /lib/pure/json.nim
parent1a8c6fb49fd9e12168895266c0067240e2bcb897 (diff)
downloadNim-49ac05ba20e017ada74d1c67944c256eefbd5d20.tar.gz
Added a higher level json module.
Diffstat (limited to 'lib/pure/json.nim')
-rw-r--r--[-rwxr-xr-x]lib/pure/json.nim708
1 files changed, 254 insertions, 454 deletions
diff --git a/lib/pure/json.nim b/lib/pure/json.nim
index 0fd2a9f01..0e5d2095b 100755..100644
--- a/lib/pure/json.nim
+++ b/lib/pure/json.nim
@@ -1,484 +1,284 @@
 #
 #
 #            Nimrod's Runtime Library
-#        (c) Copyright 2010 Andreas Rumpf
+#        (c) Copyright 2010 Andreas Rumpf, Dominik Picheta
 #
 #    See the file "copying.txt", included in this
 #    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.
+import parsejson, streams, strutils
 
-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
+type
+  TJsonNodeKind* = enum
+    JString,
+    JNumber,
+    JBool,
+    JNull,
+    JObject,
+    JArray
     
-  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
+  PJsonNode* = ref TJsonNode 
+  TJsonNode* = object
+    case kind*: TJsonNodeKind
+    of JString:
+      str*: String
+    of JNumber:
+      num*: Float
+    of JBool:
+      bval*: Bool
+    of JNull:
+      nil
+    of JObject:
+      fields*: seq[tuple[key: string, obj: PJsonNode]]
+    of JArray:
+      elems*: seq[PJsonNode]
 
-  TJsonParser* = object of TBaseLexer ## the parser object.
-    a: string
-    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"
-  ]
+  EJsonParsingError* = object of EBase
 
-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 raiseParseErr(parser: TJsonParser, msg: string, line = True) =
+  if line:
+    raise newException(EJsonParsingError, "(" & $parser.getLine & ", " &
+                       $parser.getColumn & ") " & msg)
+  else:
+    raise newException(EJsonParsingError, msg)
 
-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 indent(s: var string, i: int) = 
+  s.add(repeatChar(i))
 
-proc number*(my: TJsonParser): float {.inline.} = 
-  ## returns the number for the event: ``jsonNumber``
-  assert(my.kind == jsonNumber)
-  return parseFloat(my.a)
+proc newIndent(curr, indent: int, ml: bool): Int =
+  if ml: return curr + indent
+  else: return indent
 
-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 nl(s: var string, ml: bool) = 
+  if ml: s.add("\n")
 
-proc getLine*(my: TJsonParser): int {.inline.} = 
-  ## get the current line the parser has arrived at.
-  result = my.linenumber
+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)
+    result.indent(currIndent) # Indentation
+    result.add("{")
+    result.nl(ml) # New line
+    for i in 0..len(node.fields)-1:
+      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))
+    result.nl(ml)
+    result.indent(currIndent) # indent the same as {
+    result.add("}")
+  of JString: 
+    if lstArr: result.indent(currIndent)
+    result.add("\"" & node.str & "\"")
+  of JNumber:
+    if lstArr: result.indent(currIndent)
+    result.add($node.num)
+  of JBool:
+    if lstArr: result.indent(currIndent)
+    result.add($node.bval)
+  of JArray:
+    if len(node.elems) != 0:
+      result.add("[")
+      result.nl(ml)
+      for i in 0..len(node.elems)-1:
+        if i > 0:
+          result.add(", ")
+          result.nl(ml) # New Line
+        toPretty(result, node.elems[i], indent, ml,
+            True, newIndent(currIndent, indent, ml))
+      result.nl(ml)
+      result.indent(currIndent)
+      result.add("]")
+    else: result.add("[]")
+  of JNull:
+    if lstArr: result.indent(currIndent)
+    result.add("null")
 
-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 pretty*(node: PJsonNode, indent = 2): String =
+  ## Converts a `PJsonNode` to its JSON Representation, with indentation and
+  ## on multiple lines.
+  result = ""
+  toPretty(result, node, indent)
 
-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 `$`*(node: PJsonNode): String =
+  ## Converts a `PJsonNode` to its JSON Representation on one line.
+  result = ""
+  toPretty(result, node, 1, False)
 
-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 newJString*(s: String): PJsonNode =
+  ## Creates a new `JString PJsonNode`
+  new(result)
+  result.kind = JString
+  result.str = s
 
-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 newJNumber*(n: Float): PJsonNode =
+  ## Creates a new `JNumber PJsonNode`
+  new(result)
+  result.kind = JNumber
+  result.num  = n
   
-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 newJBool*(b: Bool): PJsonNode =
+  ## Creates a new `JBool PJsonNode`
+  new(result)
+  result.kind = JBool
+  result.bval = b
 
-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 newJNull*(): PJsonNode =
+  ## Creates a new `JNull PJsonNode`
+  new(result)
+  result.kind = JNull
 
-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 newJObject*(f: seq[tuple[key: string, obj: PJsonNode]]): PJsonNode =
+  ## Creates a new `JObject PJsonNode`
+  new(result)
+  result.kind = JObject
+  result.fields = f
 
-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
-
-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
+proc newJArray*(a: seq[PJsonNode]): PJsonNode =
+  ## Creates a new `JArray PJsonNode`
+  new(result)
+  result.kind = JArray
+  result.elems = a
   
-when isMainModule:
-  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
+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
     of jsonError:
-      Echo(x.errorMsg())
+      parser.raiseParseErr(parser.errorMsg(), false)
       break
     of jsonEof: break
-    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("]")
+    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()
     
-  close(x)
+  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
+
+
+
+# { "json": 5 } 
+# To get that we shall use, obj["json"]
+
+when isMainModule:
+  #var node = parse("{ \"test\": null }")
+  #echo(node.existsKey("test56"))
+  var parsed = parseFile("test2.json")
+  echo(parsed["commits"][0]["author"]["username"].str)
+  echo()
+  echo(pretty(parsed, 2))
+  echo()
+  echo(parsed)
+
+  discard """
+  while true:
+    var json = stdin.readLine()
+    var node = parse(json)
+    echo(node)
+    echo()
+    echo()
+  """