Skip to content

Commit 678afde

Browse files
committed
Add json-in-json.
1 parent e3aa56f commit 678afde

File tree

4 files changed

+156
-7
lines changed

4 files changed

+156
-7
lines changed

README.md

+15-2
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ Gives us:
222222
"10/13"
223223
```
224224

225-
### Static writing with `toStaticJson`.
225+
## Static writing with `toStaticJson`.
226226

227227
Some times you have or const json and you want to write it in a static way. There is a special function for that:
228228

@@ -232,7 +232,7 @@ thing.toStaticJson()
232232

233233
Make sure `thing` is a `static` or a `const` value and you will get a compile time string with your JSON.
234234

235-
### Full support for case variant objects.
235+
## Full support for case variant objects.
236236

237237
Case variant objects like this are fully supported:
238238

@@ -246,3 +246,16 @@ t=oe RefNode = ref object
246246
The discriminator do no have to come first, if they do come in the middle this
247247
library will scan the object, find the discriminator field, then rewind and
248248
parse the object normally.
249+
250+
251+
## Full support for json-in-json.
252+
253+
Some times your json objects could contain arbitrary json structures,
254+
maybe event user defined, that could only be walked as json nodes. This library allows you to parse json-in-json were you parse some of the structure as real nim objects but leave some parts of it as Json Nodes to be walked later with code:
255+
256+
```nim
257+
type Entry = object
258+
name: string
259+
data: JsonNode
260+
entry.toJson.fromJson(Entry)
261+
```

src/jsony.nim

+101-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import jsony/objvar, strutils, tables, unicode
1+
import jsony/objvar, strutils, tables, unicode, json
22

33
type JsonError* = object of ValueError
44

@@ -13,6 +13,7 @@ proc parseHook*[T: object|ref object](s: string, i: var int, v: var T)
1313
proc parseHook*[T](s: string, i: var int, v: var Table[string, T])
1414
proc parseHook*[T: tuple](s: string, i: var int, v: var T)
1515
proc parseHook*[T: array](s: string, i: var int, v: var T)
16+
proc parseHook*(s: string, i: var int, v: var JsonNode)
1617

1718
template error(msg: string, i: int) =
1819
## Shortcut to raise an exception.
@@ -334,6 +335,66 @@ proc parseHook*[T](s: string, i: var int, v: var Table[string, T]) =
334335
break
335336
eatChar(s, i, '}')
336337

338+
proc parseHook*(s: string, i: var int, v: var JsonNode) =
339+
## Parses a regular json node.
340+
eatSpace(s, i)
341+
if i < s.len and s[i] == '{':
342+
v = newJObject()
343+
eatChar(s, i, '{')
344+
while i < s.len:
345+
eatSpace(s, i)
346+
if i < s.len and s[i] == '}':
347+
break
348+
var k: string
349+
parseHook(s, i, k)
350+
eatChar(s, i, ':')
351+
var e: JsonNode
352+
parseHook(s, i, e)
353+
v[k] = e
354+
eatSpace(s, i)
355+
if i < s.len and s[i] == ',':
356+
inc i
357+
eatChar(s, i, '}')
358+
elif i < s.len and s[i] == '[':
359+
v = newJArray()
360+
eatChar(s, i, '[')
361+
while i < s.len:
362+
eatSpace(s, i)
363+
if i < s.len and s[i] == ']':
364+
break
365+
var e: JsonNode
366+
parseHook(s, i, e)
367+
v.add(e)
368+
eatSpace(s, i)
369+
if i < s.len and s[i] == ',':
370+
inc i
371+
eatChar(s, i, ']')
372+
elif i < s.len and s[i] == '"':
373+
var str: string
374+
parseHook(s, i, str)
375+
v = newJString(str)
376+
else:
377+
var data = parseSymbol(s, i)
378+
if data == "null":
379+
v = newJNull()
380+
elif data == "true":
381+
v = newJBool(true)
382+
elif data == "false":
383+
v = newJBool(false)
384+
elif data.len > 0 and data[0] in {'0'..'9'}:
385+
if "." in data:
386+
try:
387+
v = newJFloat(parseFloat(data))
388+
except ValueError:
389+
error("Invalid integer.", i)
390+
else:
391+
try:
392+
v = newJInt(parseInt(data))
393+
except ValueError:
394+
error("Invalid float.", i)
395+
else:
396+
error("Unexpected.", i)
397+
337398
proc fromJson*[T](s: string, x: typedesc[T]): T =
338399
## Takes json and outputs the object it represents.
339400
## * Extra json fields are ignored.
@@ -342,6 +403,11 @@ proc fromJson*[T](s: string, x: typedesc[T]): T =
342403
var i = 0
343404
s.parseHook(i, result)
344405

406+
proc fromJson*(s: string): JsonNode =
407+
## Takes json parses it into `JsonNode`s.
408+
var i = 0
409+
s.parseHook(i, result)
410+
345411
proc dumpHook*(s: var string, v: bool)
346412
proc dumpHook*(s: var string, v: uint|uint8|uint16|uint32|uint64)
347413
proc dumpHook*(s: var string, v: int|int8|int16|int32|int64)
@@ -526,6 +592,40 @@ proc dumpHook*(s: var string, v: ref object) =
526592
else:
527593
s.dumpHook(v[])
528594

595+
proc dumpHook*(s: var string, v: JsonNode) =
596+
## Dumps a regular json node.
597+
case v.kind:
598+
of JObject:
599+
s.add '{'
600+
var i = 0
601+
for k, e in v.pairs:
602+
if i != 0:
603+
s.add ","
604+
s.dumpHook(k)
605+
s.add ':'
606+
s.dumpHook(e)
607+
inc i
608+
s.add '}'
609+
of JArray:
610+
s.add '['
611+
var i = 0
612+
for e in v:
613+
if i != 0:
614+
s.add ","
615+
s.dumpHook(e)
616+
inc i
617+
s.add ']'
618+
of JNull:
619+
s.add "null"
620+
of JInt:
621+
s.dumpHook(v.getInt)
622+
of JFloat:
623+
s.dumpHook(v.getFloat)
624+
of JString:
625+
s.dumpHook(v.getStr)
626+
of JBool:
627+
s.dumpHook(v.getBool)
628+
529629
proc toJson*[T](v: T): string =
530630
dumpHook(result, v)
531631

tests/fuzz.nim

+17-4
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ for i in 0 ..< 10000:
4545
data = treeStr
4646
pos = rand(data.len)
4747
value = rand(255).char
48-
# pos = 2284
49-
# value = 152.char
48+
#pos = 18716
49+
#value = 125.char
5050

5151
data[pos] = value
5252
echo &"{i} {pos} {value.uint8}"
@@ -56,9 +56,22 @@ for i in 0 ..< 10000:
5656
except JsonError:
5757
discard
5858

59-
data = data[0 ..< pos]
59+
var data2 = data[0 ..< pos]
6060
try:
61-
let node = data.fromJson(Node)
61+
let node = data2.fromJson(Node)
62+
doAssert node != nil
63+
except JsonError:
64+
discard
65+
66+
# JsonNode
67+
try:
68+
let node = data.fromJson()
69+
doAssert node != nil
70+
except JsonError:
71+
discard
72+
73+
try:
74+
let node = data2.fromJson()
6275
doAssert node != nil
6376
except JsonError:
6477
discard

tests/test_json_in_json.nim

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import jsony, strutils, json
2+
3+
block:
4+
type Entry = object
5+
name: string
6+
data: JsonNode
7+
8+
var entry = Entry()
9+
entry.name = "json-in-json"
10+
entry.data = %*{
11+
"random-data": "here",
12+
"number": 123,
13+
"number2": 123.456,
14+
"array": @[1, 2, 3],
15+
"active": true,
16+
"null": nil
17+
}
18+
19+
doAssert entry.toJson() == """{"name":"json-in-json","data":{"random-data":"here","number":123,"number2":123.456,"array":[1,2,3],"active":true,"null":null}}"""
20+
doAssert $entry.toJson.fromJson(Entry) == """(name: "json-in-json", data: {"random-data":"here","number":123,"number2":123.456,"array":[1,2,3],"active":true,"null":null})"""
21+
22+
let s = """{"name":"json-in-json","data":{"random-data":"here","number":123,"number2":123.456,"array":[1,2,3],"active":true,"null":null}}"""
23+
doAssert $s.fromJson() == """{"name":"json-in-json","data":{"random-data":"here","number":123,"number2":123.456,"array":[1,2,3],"active":true,"null":null}}"""

0 commit comments

Comments
 (0)