Skip to content

Commit 227f24d

Browse files
committed
Big API change + fuzzing.
1 parent 295a07c commit 227f24d

15 files changed

+245
-120
lines changed

jsony.nimble

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version = "0.0.2"
1+
version = "0.0.3"
22
author = "Andre von Houck"
33
description = "A loose direct to object json parser with hooks."
44
license = "MIT"

src/jsony.nim

Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import macros, strutils, tables, unicode
22

3-
type JsonError = object of ValueError
3+
type JsonError* = object of ValueError
44

55
const whiteSpace = {' ', '\n', '\t', '\r'}
66

7+
when defined(release):
8+
{.push checks: off.}
9+
710
proc parseHook*[T](s: string, i: var int, v: var seq[T])
811
proc parseHook*[T: enum](s: string, i: var int, v: var T)
912
proc parseHook*[T: object|ref object](s: string, i: var int, v: var T)
@@ -61,10 +64,10 @@ proc parseHook*(s: string, i: var int, v: var bool) =
6164
else:
6265
# Its faster to do char by char scan:
6366
eatSpace(s, i)
64-
if i + 3 < s.len and s[i+0] == 't' or s[i+1] == 'r' or s[i+2] == 'u' or s[i+3] == 'e':
67+
if i + 3 < s.len and s[i+0] == 't' and s[i+1] == 'r' and s[i+2] == 'u' and s[i+3] == 'e':
6568
i += 4
6669
v = true
67-
elif i + 4 < s.len and s[i+0] == 'f' or s[i+1] == 'a' or s[i+2] == 'l' or s[i+3] == 's' or s[i+4] == 'e':
70+
elif i + 4 < s.len and s[i+0] == 'f' and s[i+1] == 'a' and s[i+2] == 'l' and s[i+3] == 's' and s[i+4] == 'e':
6871
i += 5
6972
v = false
7073
else:
@@ -76,10 +79,14 @@ proc parseHook*(s: string, i: var int, v: var SomeUnsignedInt) =
7679
v = type(v)(parseInt(parseSymbol(s, i)))
7780
else:
7881
eatSpace(s, i)
79-
var v2: uint64 = 0
82+
var
83+
v2: uint64 = 0
84+
startI = i
8085
while i < s.len and s[i] in {'0'..'9'}:
8186
v2 = v2 * 10 + (s[i].ord - '0'.ord).uint64
8287
inc i
88+
if startI == i:
89+
error("Number expected.", i)
8390
v = type(v)(v2)
8491

8592
proc parseHook*(s: string, i: var int, v: var SomeSignedInt) =
@@ -88,15 +95,18 @@ proc parseHook*(s: string, i: var int, v: var SomeSignedInt) =
8895
v = type(v)(parseInt(parseSymbol(s, i)))
8996
else:
9097
eatSpace(s, i)
91-
if s[i] == '-':
98+
if i < s.len and s[i] == '-':
9299
var v2: uint64
93100
inc i
94101
parseHook(s, i, v2)
95102
v = -type(v)(v2)
96103
else:
97104
var v2: uint64
98105
parseHook(s, i, v2)
99-
v = type(v)(v2)
106+
try:
107+
v = type(v)(v2)
108+
except:
109+
error("Number type to small to contain the number.", i)
100110

101111
proc parseHook*(s: string, i: var int, v: var SomeFloat) =
102112
## Will parse float32 and float64.
@@ -141,13 +151,13 @@ proc parseHook*[T](s: string, i: var int, v: var seq[T]) =
141151
eatChar(s, i, '[')
142152
while i < s.len:
143153
eatSpace(s, i)
144-
if s[i] == ']':
154+
if i < s.len and s[i] == ']':
145155
break
146156
var element: T
147157
parseHook(s, i, element)
148158
v.add(element)
149159
eatSpace(s, i)
150-
if s[i] == ',':
160+
if i < s.len and s[i] == ',':
151161
inc i
152162
else:
153163
break
@@ -161,7 +171,7 @@ proc parseHook*[T: tuple](s: string, i: var int, v: var T) =
161171
eatSpace(s, i)
162172
parseHook(s, i, value)
163173
eatSpace(s, i)
164-
if s[i] == ',':
174+
if i < s.len and s[i] == ',':
165175
inc i
166176
eatChar(s, i, ']')
167177

@@ -173,38 +183,38 @@ proc parseHook*[T: array](s: string, i: var int, v: var T) =
173183
eatSpace(s, i)
174184
parseHook(s, i, value)
175185
eatSpace(s, i)
176-
if s[i] == ',':
186+
if i < s.len and s[i] == ',':
177187
inc i
178188
eatChar(s, i, ']')
179189

180190
proc skipValue(s: string, i: var int) =
181191
## Used to skip values of extra fields.
182192
eatSpace(s, i)
183-
if s[i] == '{':
193+
if i < s.len and s[i] == '{':
184194
eatChar(s, i, '{')
185195
while i < s.len:
186196
eatSpace(s, i)
187-
if s[i] == '}':
197+
if i < s.len and s[i] == '}':
188198
break
189199
skipValue(s, i)
190200
eatChar(s, i, ':')
191201
skipValue(s, i)
192202
eatSpace(s, i)
193-
if s[i] == ',':
203+
if i < s.len and s[i] == ',':
194204
inc i
195205
eatChar(s, i, '}')
196-
elif s[i] == '[':
206+
elif i < s.len and s[i] == '[':
197207
eatChar(s, i, '[')
198208
while i < s.len:
199209
eatSpace(s, i)
200-
if s[i] == ']':
210+
if i < s.len and s[i] == ']':
201211
break
202212
skipValue(s, i)
203213
eatSpace(s, i)
204-
if s[i] == ',':
214+
if i < s.len and s[i] == ',':
205215
inc i
206216
eatChar(s, i, ']')
207-
elif s[i] == '"':
217+
elif i < s.len and s[i] == '"':
208218
var str: string
209219
parseHook(s, i, str)
210220
else:
@@ -264,25 +274,25 @@ macro fieldsMacro(v: typed, key: string) =
264274
proc parseHook*[T: enum](s: string, i: var int, v: var T) =
265275
eatSpace(s, i)
266276
var strV: string
267-
if s[i] == '"':
277+
if i < s.len and s[i] == '"':
268278
parseHook(s, i, strV)
269279
when compiles(enumHook(strV, v)):
270280
enumHook(strV, v)
271281
else:
272-
v = parseEnum[T](strV)
282+
try:
283+
v = parseEnum[T](strV)
284+
except:
285+
error("Can't parse enum.", i)
273286
else:
274-
strV = parseSymbol(s, i)
275-
v = T(parseInt(strV))
287+
try:
288+
strV = parseSymbol(s, i)
289+
v = T(parseInt(strV))
290+
except:
291+
error("Can't parse enum.", i)
276292

277293
proc parseHook*[T: object|ref object](s: string, i: var int, v: var T) =
278294
## Parse an object.
279295
eatSpace(s, i)
280-
# if s[i] == 'n':
281-
# let what = parseSymbol(s, i)
282-
# if what == "null":
283-
# return
284-
# else:
285-
# error("Expected {} or null.", i)
286296
if i + 3 < s.len and s[i+0] == 'n' and s[i+1] == 'u' and s[i+2] == 'l' and s[i+3] == 'l':
287297
i += 4
288298
return
@@ -293,7 +303,7 @@ proc parseHook*[T: object|ref object](s: string, i: var int, v: var T) =
293303
new(v)
294304
while i < s.len:
295305
eatSpace(s, i)
296-
if s[i] == '}':
306+
if i < s.len and s[i] == '}':
297307
break
298308
var key: string
299309
parseHook(s, i, key)
@@ -302,7 +312,7 @@ proc parseHook*[T: object|ref object](s: string, i: var int, v: var T) =
302312
renameHook(v, key)
303313
fieldsMacro(v, key)
304314
eatSpace(s, i)
305-
if s[i] == ',':
315+
if i < s.len and s[i] == ',':
306316
inc i
307317
else:
308318
break
@@ -315,35 +325,44 @@ proc parseHook*[T](s: string, i: var int, v: var Table[string, T]) =
315325
eatChar(s, i, '{')
316326
while i < s.len:
317327
eatSpace(s, i)
318-
if s[i] == '}':
328+
if i < s.len and s[i] == '}':
319329
break
320330
var key: string
321331
parseHook(s, i, key)
322332
eatChar(s, i, ':')
323333
var element: T
324334
parseHook(s, i, element)
325335
v[key] = element
326-
if s[i] == ',':
336+
if i < s.len and s[i] == ',':
327337
inc i
328338
else:
329339
break
330340
eatChar(s, i, '}')
331341

332-
proc fromJson*[T](s: string): T =
342+
# proc fromJson*[T](s: string): T =
343+
# ## Takes json and outputs the object it represents.
344+
# ## * Extra json fields are ignored.
345+
# ## * Missing json fields keep their default values.
346+
# ## * `proc newHook(foo: var ...)` Can be used to populate default values.
347+
348+
# var i = 0
349+
# parseHook(s, i, result)
350+
351+
proc fromJson*[T](s: string, x: typedesc[T]): T =
333352
## Takes json and outputs the object it represents.
334353
## * Extra json fields are ignored.
335354
## * Missing json fields keep their default values.
336355
## * `proc newHook(foo: var ...)` Can be used to populate default values.
337-
338356
var i = 0
339-
parseHook(s, i, result)
357+
s.parseHook(i, result)
340358

341359
proc dumpHook*(s: var string, v: bool)
342360
proc dumpHook*(s: var string, v: uint|uint8|uint16|uint32|uint64)
343361
proc dumpHook*(s: var string, v: int|int8|int16|int32|int64)
344362
proc dumpHook*(s: var string, v: string)
345363
proc dumpHook*(s: var string, v: char)
346364
proc dumpHook*(s: var string, v: tuple)
365+
proc dumpHook*(s: var string, v: enum)
347366
proc dumpHook*[N, T](s: var string, v: array[N, T])
348367
proc dumpHook*[T](s: var string, v: seq[T])
349368
proc dumpHook*(s: var string, v: object)
@@ -355,9 +374,6 @@ proc dumpHook*(s: var string, v: bool) =
355374
else:
356375
s.add "false"
357376

358-
when defined(release):
359-
{.push checks: off.}
360-
361377
const lookup = block:
362378
## Generate 00, 01, 02 ... 99 pairs.
363379
var s = ""
@@ -408,9 +424,6 @@ proc dumpHook*(s: var string, v: int|int8|int16|int32|int64) =
408424
else:
409425
dumpHook(s, v.uint64)
410426

411-
when defined(release):
412-
{.pop.}
413-
414427
proc dumpHook*(s: var string, v: SomeFloat) =
415428
s.add $v
416429

@@ -478,6 +491,9 @@ proc dumpHook*(s: var string, v: tuple) =
478491
inc i
479492
s.add ']'
480493

494+
proc dumpHook*(s: var string, v: enum) =
495+
s.dumpHook($v)
496+
481497
proc dumpHook*[N, T](s: var string, v: array[N, T]) =
482498
s.add '['
483499
var i = 0
@@ -541,3 +557,7 @@ template toStaticJson*(v: untyped): static[string] =
541557
# ## This will turn v into json at compile time and return the json string.
542558
# const s = v.toJsonDynamic()
543559
# s
560+
561+
562+
when defined(release):
563+
{.pop.}

tests/bench.nim

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ var treeStr = tree.toJson()
3838
echo treeStr[0 ..< 100]
3939
echo genId, " node tree:"
4040

41+
timeIt "treeform/jsony", 100:
42+
keep treeStr.fromJson(Node)
43+
4144
when not defined(gcArc):
4245
timeIt "status-im/nim-json-serialization", 100:
4346
keep json_serialization.Json.decode(treeStr, Node)
4447

45-
doAssert json_serialization.Json.decode(treeStr, Node).toJson() == treeStr
46-
47-
timeIt "treeform/jsony", 100:
48-
keep jsony.fromJson[Node](treeStr)
48+
timeIt "planetis-m/eminim", 100:
49+
keep newStringStream(treeStr).jsonTo(Node)
4950

5051
when defined(packedjson):
5152
timeIt "araq/packedjson", 100:
@@ -54,9 +55,6 @@ else:
5455
timeIt "nim std/json", 100:
5556
keep json.to(json.parseJson(treeStr), Node)
5657

57-
timeIt "planetis-m/eminim", 100:
58-
keep newStringStream(treeStr).jsonTo(Node)
59-
6058
echo "serialize:"
6159

6260
timeIt "treeform/jsony", 100:

tests/bench_parts.nim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ block:
1515
var jsonStr = "\"hello there how are you?\""
1616
timeIt "treeform/jsony", 100:
1717
for i in 0 ..< 1000:
18-
keep fromJson[string](jsonStr)
18+
keep jsonStr.fromJson(string)
1919

2020
when not defined(gcArc):
2121
timeIt "status-im/nim-json-serialization", 100:
@@ -34,7 +34,7 @@ block:
3434
var jsonStr = node.toJson()
3535
timeIt "treeform/jsony", 100:
3636
for i in 0 ..< 1000:
37-
keep fromJson[Node](jsonStr)
37+
keep jsonStr.fromJson(Node)
3838

3939
when not defined(gcArc):
4040
timeIt "status-im/nim-json-serialization", 100:
@@ -54,7 +54,7 @@ block:
5454
seqObj.add(Node())
5555
var jsonStr = seqObj.toJson()
5656
timeIt "treeform/jsony", 100:
57-
keep fromJson[seq[Node]](jsonStr)
57+
keep jsonStr.fromJson(seq[Node])
5858

5959
when not defined(gcArc):
6060
timeIt "status-im/nim-json-serialization", 100:

0 commit comments

Comments
 (0)