88# #
99# # It can convert Nim objects, tables and arrays to JSON strings and vice versa.
1010# # It also provides compile-time options for customizing the serialization process.
11+ # #
12+ # # This JSON implementation has a similar API to [pkg/jsony](https://github.com/treeform/jsony)
13+ # # but is designed to work with memory-mapped files and provide a more flexible and extensible
14+ # # serialization/deserialization mechanism.
1115
1216import std/ [macros, macrocache, json, sequtils,
1317 strutils, options, tables, enumutils, memfiles,
14- critbits]
18+ critbits, typetraits ]
1519
1620export json # re-exporting the standard JSON module for JsonNode and related types
1721
1822type
19- Integers * = int | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64
23+ Integers * = int | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | uint
24+
2025 AnyTable * [K, V] =
2126 Table [K, V] | OrderedTable [K, V] | TableRef [K, V] | OrderedTableRef [K, V]
27+
2228 JsonOptions * = ref object
2329 # # Options for JSON serialization
2430 pretty: bool
@@ -111,10 +117,9 @@ proc dumpHook*(s: var string, val: tuple)
111117proc dumpHook * (s: var string , val: object )
112118proc dumpHook * [T: ref object ](s: var string , val: T)
113119proc dumpHook * (s: var string , v: enum )
114-
115120proc dumpHook * [K: string , V](s: var string , val: AnyTable [K, V])
116-
117- proc tableToJson * [K: string , V](t: AnyTable [K, V]): string
121+ proc dumpHook * [T](s: var string , val: set [T])
122+ proc dumpHook * [T: distinct ](s: var string , v: T)
118123
119124proc toJson * [T](v: T, opts: JsonOptions = nil ): string =
120125 # # Convert a Nim object to its JSON string representation using dump hooks.
@@ -148,16 +153,9 @@ macro toStaticJson*(v: typed, opts: static JsonOptions = nil): untyped =
148153#
149154# Dump Hooks for JSON Serialization
150155#
151- proc dumpTableImpl [K: string , V, T](s: var string , t: T) =
152- s.add (" {" )
153- var i = 0
154- for k, item in t.pairs:
155- if i > 0 : s.add (" ," )
156- dumpHook (s, k)
157- s.add (" :" )
158- dumpHook (s, item)
159- inc i
160- s.add (" }" )
156+ proc dumpHook * [T: distinct ](s: var string , v: T) =
157+ var x = cast [T.distinctBase](v)
158+ s.dumpHook (x)
161159
162160proc dumpHook * [K: string , V](s: var string , val: AnyTable [K, V]) =
163161 # # Converts Table/OrderedTable and ref variants to JSON object.
@@ -186,6 +184,16 @@ proc dumpHook*[K: string, V](s: var string, val: AnyTable[K, V]) =
186184
187185 s.add (" }" )
188186
187+ proc dumpHook * [T](s: var string , val: set [T]) =
188+ # # Converts a set to a JSON array.
189+ s.add (" [" )
190+ var i = 0
191+ for item in val:
192+ if i > 0 : s.add (" ," )
193+ dumpHook (s, item)
194+ inc i
195+ s.add (" ]" )
196+
189197proc dumpHook * [T](s: var string , arr: seq [T]) =
190198 # # Converts a sequence of items to a JSON array string.
191199 s.add (" [" )
@@ -289,12 +297,6 @@ proc objectToJson*(v, valImpl: NimNode, opts: JsonOptions = nil): NimNode =
289297 `res`.add (" }" )
290298 `res`
291299
292- proc tableToJson * [K: string , V](t: AnyTable [K, V]): string =
293- # # Converts a Nim table to JSON string.
294- var s = " "
295- dumpTableImpl (s, t)
296- result = s
297-
298300proc arrayToJson * (v, valImpl: NimNode , opts: JsonOptions = nil ): NimNode =
299301 # # Converts a Nim array or sequence to its JSON representation.
300302 var strArrayItems = newStmtList ()
@@ -521,6 +523,7 @@ proc parseHook*[T](parser: var Parser, field: string, v: var seq[T])
521523proc parseHook * [T: ref object ](parser: var Parser , field: string , v: var T)
522524proc parseHook * [T: enum ](parser: var Parser , field: string , v: var T)
523525proc parseHook * [K: string , V](parser: var Parser , field: string , v: var AnyTable [K, V])
526+ proc parseHook * [T](parser: var Parser , field: string , v: var set [T])
524527
525528proc skipValue * (parser: var Parser )
526529
@@ -655,6 +658,27 @@ proc parseHook*[T: enum](parser: var Parser, field: string, v: var T) =
655658 else :
656659 parser.error (unexpectedTokenExpected % [$ parser.curr.kind, " string or number" ])
657660
661+ proc parseHook * [T](parser: var Parser , field: string , v: var set [T]) =
662+ # # A hook to parse set fields from JSON arrays
663+ parser.expectSkip (tkLBracket) # start of array
664+ while parser.curr.kind != tkRBracket:
665+ var item: T
666+ parser.parseHook (" " , item)
667+ v.incl (item)
668+ if parser.curr.kind == tkComma:
669+ parser.walk ()
670+ parser.expectSkip (tkRBracket) # end of array
671+
672+ proc parseHook * [T: distinct ](parser: var Parser , field: string , v: var T) =
673+ # # A hook to parse distinct types by parsing their base type and then converting
674+ var tmp: T.distinctBase
675+ parser.parseHook (" " , tmp)
676+ v = T (tmp)
677+
678+ proc parseHook * [T: Integers ](parser: var Parser , field: string , v: var T) =
679+ # # A hook to parse integer fields
680+ v = cast [v.type ](parser.curr.value.get ().parseInt ())
681+
658682macro getObjectFields (obj: typed ): untyped =
659683 let objImpl = obj.getType ()
660684 let tempFields =
@@ -675,7 +699,7 @@ macro getObjectFields(obj: typed): untyped =
675699 else : discard
676700 result = newStmtList ().add (nnkPrefix.newTree (ident " @" , fieldList))
677701
678- proc parseHook * (parser: var Parser , field: string , v: var object ) =
702+ proc parseHook * [T: object | ref object ] (parser: var Parser , field: string , v: var T ) =
679703 # # Custom parsing for Address field
680704 parser.expectSkip (tkLBrace) # start of object
681705 const objectFields: seq [string ] = getObjectFields (v)
@@ -689,8 +713,19 @@ proc parseHook*(parser: var Parser, field: string, v: var object) =
689713 if key == objField:
690714 parser.walk () # advance to field name
691715 parser.expectSkip (tkColon)
692- # call the appropriate parseHook based on field type
693- parser.parseHook (objField, objVal)
716+
717+ when compiles (parser.parseHook (objField, objVal)):
718+ # normal mutable fields
719+ parser.parseHook (objField, objVal)
720+ else :
721+ # immutable fields (e.g. variant discriminator like `type`)
722+ var tmp: type (objVal)
723+ parser.parseHook (objField, tmp)
724+ when compiles (objVal = tmp):
725+ objVal = tmp
726+ else :
727+ parser.error (" Field `" & objField & " ` is immutable" )
728+
694729 if parser.curr.kind != tkRBrace:
695730 parser.walk () # advance to next token
696731 break all
0 commit comments