mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-03 11:42:33 +00:00
285 lines
7.6 KiB
Nim
285 lines
7.6 KiB
Nim
#
|
|
#
|
|
# Nimrod's Runtime Library
|
|
# (c) Copyright 2010 Andreas Rumpf, Dominik Picheta
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
import parsejson, streams, strutils
|
|
|
|
type
|
|
TJsonNodeKind* = enum
|
|
JString,
|
|
JNumber,
|
|
JBool,
|
|
JNull,
|
|
JObject,
|
|
JArray
|
|
|
|
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]
|
|
|
|
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 indent(s: var string, i: int) =
|
|
s.add(repeatChar(i))
|
|
|
|
proc newIndent(curr, indent: int, ml: bool): Int =
|
|
if ml: return curr + indent
|
|
else: return indent
|
|
|
|
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) =
|
|
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 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 `$`*(node: PJsonNode): String =
|
|
## Converts a `PJsonNode` 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 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)
|
|
result.kind = JNull
|
|
|
|
proc newJObject*(f: seq[tuple[key: string, obj: PJsonNode]]): PJsonNode =
|
|
## Creates a new `JObject PJsonNode`
|
|
new(result)
|
|
result.kind = JObject
|
|
result.fields = f
|
|
|
|
proc newJArray*(a: seq[PJsonNode]): PJsonNode =
|
|
## Creates a new `JArray PJsonNode`
|
|
new(result)
|
|
result.kind = JArray
|
|
result.elems = a
|
|
|
|
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:
|
|
parser.raiseParseErr(parser.errorMsg(), false)
|
|
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()
|
|
|
|
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()
|
|
"""
|