Skip to content

Commit 3cb486d

Browse files
authored
Flavor automatic serialization (#122)
* Flavor automatic serialization * Range is a typeclass * Add range typeclass * Add distinct typeclass * cleanup * Fix import * Does not works with nim 1.6 * fix
1 parent 5326730 commit 3cb486d

File tree

4 files changed

+158
-6
lines changed

4 files changed

+158
-6
lines changed

json_serialization/format.nim

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,48 @@
88
# those terms.
99

1010
import
11-
serialization/[formats, object_serialization]
11+
serialization/[formats, object_serialization],
12+
./types
13+
14+
from std/json import JsonNode
15+
16+
export formats, JsonNode
17+
18+
template generateJsonAutoSerializationAddon*(FLAVOR: typed) {.dirty.} =
19+
generateAutoSerializationAddon(FLAVOR)
20+
21+
template automaticPrimitivesSerialization*(F: type FLAVOR, enable: static[bool]) =
22+
## Set all supported primitives automatic serialization flag.
23+
static:
24+
F.setAutoSerialize(string, enable)
25+
F.setAutoSerialize(seq[char], enable)
26+
F.setAutoSerialize(bool, enable)
27+
F.setAutoSerialize(ref, enable)
28+
F.setAutoSerialize(ptr, enable)
29+
F.setAutoSerialize(enum, enable)
30+
F.setAutoSerialize(SomeInteger, enable)
31+
F.setAutoSerialize(SomeFloat, enable)
32+
F.setAutoSerialize(seq, enable)
33+
F.setAutoSerialize(array, enable)
34+
F.setAutoSerialize(cstring, enable)
35+
F.setAutoSerialize(openArray[char], enable)
36+
F.setAutoSerialize(openArray, enable)
37+
F.setAutoSerialize(range, enable)
38+
F.setAutoSerialize(distinct, enable)
39+
40+
template automaticBuiltinSerialization*(F: type FLAVOR, enable: static[bool]) =
41+
## Enable or disable all builtin serialization.
42+
automaticPrimitivesSerialization(F, enable)
43+
static:
44+
F.setAutoSerialize(JsonString, enable)
45+
F.setAutoSerialize(JsonNode, enable)
46+
F.setAutoSerialize(JsonNumber, enable)
47+
F.setAutoSerialize(JsonVoid, enable)
48+
F.setAutoSerialize(JsonValueRef, enable)
49+
F.setAutoSerialize(object, enable)
50+
F.setAutoSerialize(tuple, enable)
51+
1252

13-
export formats
1453

1554
serializationFormat Json,
1655
mimeType = "application/json"
@@ -44,6 +83,11 @@ template flavorEnumRep*(T: type Json, rep: static[EnumRepresentation]) =
4483
static:
4584
DefaultFlavorEnumRep = rep
4685

86+
when declared(macrocache.hasKey): # Nim 1.6 have no macrocache.hasKey
87+
# Keep backward compatibility behavior, DefaultFlavor always enable all built in serialization.
88+
generateJsonAutoSerializationAddon(DefaultFlavor)
89+
DefaultFlavor.automaticBuiltinSerialization(true)
90+
4791
# We create overloads of these traits to force the mixin treatment of the symbols
4892
type DummyFlavor* = object
4993
template flavorUsesAutomaticObjectSerialization*(T: type DummyFlavor): bool = true
@@ -52,6 +96,10 @@ template flavorRequiresAllFields*(T: type DummyFlavor): bool = false
5296
template flavorAllowsUnknownFields*(T: type DummyFlavor): bool = false
5397
template flavorSkipNullFields*(T: type DummyFlavor): bool = false
5498

99+
when declared(macrocache.hasKey): # Nim 1.6 have no macrocache.hasKey
100+
generateJsonAutoSerializationAddon(DummyFlavor)
101+
DummyFlavor.automaticBuiltinSerialization(false)
102+
55103
template createJsonFlavor*(FlavorName: untyped,
56104
mimeTypeValue = "application/json",
57105
automaticObjectSerialization = false,
@@ -82,3 +130,12 @@ template createJsonFlavor*(FlavorName: untyped,
82130
template flavorEnumRep*(T: type FlavorName, rep: static[EnumRepresentation]) =
83131
static:
84132
`FlavorName EnumRep` = rep
133+
134+
when declared(macrocache.hasKey): # Nim 1.6 have no macrocache.hasKey
135+
generateJsonAutoSerializationAddon(FlavorName)
136+
137+
# Set default to true for backward compatibility
138+
# but user can call it again later with different value.
139+
# Or fine tuning use `Flavor.automaticSerialization(type, true/false)`
140+
FlavorName.automaticBuiltinSerialization(true)
141+

json_serialization/reader_desc.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type
2424
JsonReader*[Flavor = DefaultFlavor] = object
2525
lex*: JsonLexer
2626

27-
JsonReaderError* = object of JsonError
27+
JsonReaderError* = object of types.JsonError
2828
line*, col*: int
2929

3030
UnexpectedField* = object of JsonReaderError

json_serialization/reader_impl.nim

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,23 @@ proc readRecordValue*[T](r: var JsonReader, value: var T)
225225
else:
226226
r.raiseUnexpectedField(key, cstring typeName)
227227

228+
template autoSerializeCheck(F: distinct type, T: distinct type) =
229+
when declared(macrocache.hasKey): # Nim 1.6 have no macrocache.hasKey
230+
mixin typeAutoSerialize
231+
when not F.typeAutoSerialize(T):
232+
const typeName = typetraits.name(T)
233+
{.error: "automatic serialization is not enabled or readValue not implemented for `" &
234+
typeName & "`".}
235+
236+
template autoSerializeCheck(F: distinct type, TC: distinct type, M: distinct type) =
237+
when declared(macrocache.hasKey): # Nim 1.6 have no macrocache.hasKey
238+
mixin typeClassOrMemberAutoSerialize
239+
when not F.typeClassOrMemberAutoSerialize(TC, M):
240+
const typeName = typetraits.name(M)
241+
const typeClassName = typetraits.name(TC)
242+
{.error: "automatic serialization is not enabled or readValue not implemented for `" &
243+
typeName & "` of typeclass `" & typeClassName & "`".}
244+
228245
proc readValue*[T](r: var JsonReader, value: var T)
229246
{.raises: [SerializationError, IOError].} =
230247
## Master field/object parser. This function relies on
@@ -241,31 +258,41 @@ proc readValue*[T](r: var JsonReader, value: var T)
241258
## value = reader.readValue(int).FancyInt
242259
mixin readValue
243260

261+
type Flavor = JsonReader.Flavor
262+
244263
when value is JsonString:
264+
autoSerializeCheck(Flavor, JsonString)
245265
value = r.parseAsString()
246266

247267
elif value is JsonNode:
268+
autoSerializeCheck(Flavor, JsonNode)
248269
value = r.parseJsonNode()
249270

250271
elif value is JsonNumber:
272+
autoSerializeCheck(Flavor, JsonNumber)
251273
r.parseNumber(value)
252274

253275
elif value is JsonVoid:
276+
autoSerializeCheck(Flavor, JsonVoid)
254277
r.skipSingleJsValue()
255278

256279
elif value is JsonValueRef:
280+
autoSerializeCheck(Flavor, JsonValueRef)
257281
r.parseValue(value)
258282

259283
elif value is string:
284+
autoSerializeCheck(Flavor, string)
260285
value = r.parseString()
261286

262287
elif value is seq[char]:
288+
autoSerializeCheck(Flavor, seq[char])
263289
let val = r.parseString()
264290
value.setLen(val.len)
265291
for i in 0..<val.len:
266292
value[i] = val[i]
267293

268294
elif isCharArray(value):
295+
autoSerializeCheck(Flavor, array, typeof(value))
269296
let val = r.parseString()
270297
if val.len != value.len:
271298
# Raise tkString because we expected a `"` earlier
@@ -274,9 +301,16 @@ proc readValue*[T](r: var JsonReader, value: var T)
274301
value[i] = val[i]
275302

276303
elif value is bool:
304+
autoSerializeCheck(Flavor, bool)
277305
value = r.parseBool()
278306

279307
elif value is ref|ptr:
308+
when value is ref:
309+
autoSerializeCheck(Flavor, ref, typeof(value))
310+
311+
when value is ptr:
312+
autoSerializeCheck(Flavor, ptr, typeof(value))
313+
280314
when compiles(isNotNilCheck(value)):
281315
allocPtr value
282316
value[] = readValue(r, type(value[]))
@@ -289,26 +323,31 @@ proc readValue*[T](r: var JsonReader, value: var T)
289323
value[] = readValue(r, type(value[]))
290324

291325
elif value is enum:
326+
autoSerializeCheck(Flavor, enum, typeof(value))
292327
r.parseEnum(value)
293328

294329
elif value is SomeInteger:
330+
autoSerializeCheck(Flavor, SomeInteger, typeof(value))
295331
value = r.parseInt(typeof value,
296332
JsonReaderFlag.portableInt in r.lex.flags)
297333

298334
elif value is SomeFloat:
335+
autoSerializeCheck(Flavor, SomeFloat, typeof(value))
299336
let val = r.parseNumber(uint64)
300337
if val.isFloat:
301338
value = r.toFloat(val, typeof value)
302339
else:
303340
value = T(val.integer)
304341

305342
elif value is seq:
343+
autoSerializeCheck(Flavor, seq, typeof(value))
306344
r.parseArray:
307345
let lastPos = value.len
308346
value.setLen(lastPos + 1)
309347
readValue(r, value[lastPos])
310348

311349
elif value is array:
350+
autoSerializeCheck(Flavor, array, typeof(value))
312351
type IDX = typeof low(value)
313352
r.parseArray(idx):
314353
if idx < value.len:
@@ -318,9 +357,17 @@ proc readValue*[T](r: var JsonReader, value: var T)
318357
r.raiseUnexpectedValue("Too many items for " & $(typeof(value)))
319358

320359
elif value is (object or tuple):
360+
when declared(macrocache.hasKey): # Nim 1.6 have no macrocache.hasKey
361+
when value is object:
362+
autoSerializeCheck(Flavor, object, typeof(value))
363+
364+
when value is tuple:
365+
autoSerializeCheck(Flavor, tuple, typeof(value))
366+
367+
# Keep existing object/tuple auto serialization
368+
# But make it deprecated.
321369
mixin flavorUsesAutomaticObjectSerialization
322370

323-
type Flavor = JsonReader.Flavor
324371
const isAutomatic =
325372
flavorUsesAutomaticObjectSerialization(Flavor)
326373

json_serialization/writer.nim

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,35 +380,65 @@ template writeValue*(w: var JsonWriter, value: enum) =
380380
type Flavor = type(w).Flavor
381381
writeEnumImpl(w, value, Flavor.flavorEnumRep())
382382

383-
template isStringLike(v: string|cstring|openArray[char]|seq[char]): bool = true
383+
type
384+
StringLikeTypes = string|cstring|openArray[char]|seq[char]
385+
386+
template isStringLike(v: StringLikeTypes): bool = true
384387
template isStringLike[N](v: array[N, char]): bool = true
385388
template isStringLike(v: auto): bool = false
386389

390+
template autoSerializeCheck(F: distinct type, T: distinct type) =
391+
when declared(macrocache.hasKey): # Nim 1.6 have no macrocache.hasKey
392+
mixin typeAutoSerialize
393+
when not F.typeAutoSerialize(T):
394+
const typeName = typetraits.name(T)
395+
{.error: "automatic serialization is not enabled or writeValue not implemented for `" &
396+
typeName & "`".}
397+
398+
template autoSerializeCheck(F: distinct type, TC: distinct type, M: distinct type) =
399+
when declared(macrocache.hasKey): # Nim 1.6 have no macrocache.hasKey
400+
mixin typeClassOrMemberAutoSerialize
401+
when not F.typeClassOrMemberAutoSerialize(TC, M):
402+
const typeName = typetraits.name(M)
403+
const typeClassName = typetraits.name(TC)
404+
{.error: "automatic serialization is not enabled or writeValue not implemented for `" &
405+
typeName & "` of typeclass `" & typeClassName & "`".}
406+
387407
proc writeValue*[V: not void](w: var JsonWriter, value: V) {.raises: [IOError].} =
388408
## Write a generic value as JSON, using type-based dispatch. Overload this
389409
## function to provide custom conversions of your own types.
390410
mixin writeValue
391411

412+
type Flavor = JsonWriter.Flavor
413+
392414
when value is JsonNode:
415+
autoSerializeCheck(Flavor, JsonNode)
393416
w.streamElement(s):
394417
s.write if w.hasPrettyOutput: value.pretty
395418
else: $value
396419

397420
elif value is JsonString:
421+
autoSerializeCheck(Flavor, JsonString)
398422
w.streamElement(s):
399423
s.write $value
400424

401425
elif value is JsonVoid:
426+
autoSerializeCheck(Flavor, JsonVoid)
402427
discard
403428

404429
elif value is ref:
430+
autoSerializeCheck(Flavor, ref, typeof(value))
405431
if value.isNil:
406432
w.streamElement(s):
407433
s.write "null"
408434
else:
409435
writeValue(w, value[])
410436

411437
elif isStringLike(value):
438+
when value isnot array:
439+
autoSerializeCheck(Flavor, StringLikeTypes, typeof(value))
440+
when value is array:
441+
autoSerializeCheck(Flavor, array, typeof(value))
412442
w.streamElement(s):
413443
when value is cstring:
414444
if value == nil:
@@ -439,36 +469,54 @@ proc writeValue*[V: not void](w: var JsonWriter, value: V) {.raises: [IOError].}
439469
s.write '"'
440470

441471
elif value is bool:
472+
autoSerializeCheck(Flavor, bool)
442473
w.streamElement(s):
443474
s.write if value: "true" else: "false"
444475

445476
elif value is range:
477+
autoSerializeCheck(Flavor, range, typeof(value))
446478
when low(typeof(value)) < 0:
447479
w.writeValue int64(value)
448480
else:
449481
w.writeValue uint64(value)
450482

451483
elif value is SomeInteger:
484+
autoSerializeCheck(Flavor, SomeInteger, typeof(value))
452485
w.streamElement(s):
453486
s.writeText value
454487

455488
elif value is SomeFloat:
489+
autoSerializeCheck(Flavor, SomeFloat, typeof(value))
456490
w.streamElement(s):
457491
# TODO Implement writeText for floats
458492
# to avoid the allocation here:
459493
s.write $value
460494

461495
elif value is (seq or array or openArray) or
462496
(value is distinct and distinctBase(value) is (seq or array or openArray)):
497+
498+
when value is seq or(value is distinct and distinctBase(value) is seq):
499+
autoSerializeCheck(Flavor, seq, typeof(value))
500+
when value is array or(value is distinct and distinctBase(value) is array):
501+
autoSerializeCheck(Flavor, array, typeof(value))
502+
when value is openArray or(value is distinct and distinctBase(value) is openArray):
503+
autoSerializeCheck(Flavor, openArray, typeof(value))
463504
when value is distinct:
464505
w.writeArray(distinctBase value)
465506
else:
466507
w.writeArray(value)
467508

468509
elif value is (distinct or object or tuple):
510+
when declared(macrocache.hasKey):# Nim 1.6 have no macrocache.hasKey
511+
when value is object:
512+
autoSerializeCheck(Flavor, object, typeof(value))
513+
when value is tuple:
514+
autoSerializeCheck(Flavor, tuple, typeof(value))
515+
when value is distinct:
516+
autoSerializeCheck(Flavor, distinct, typeof(value))
517+
469518
mixin flavorUsesAutomaticObjectSerialization
470519

471-
type Flavor = JsonWriter.Flavor
472520
const isAutomatic =
473521
flavorUsesAutomaticObjectSerialization(Flavor)
474522

0 commit comments

Comments
 (0)