Initial JSON serializer

This commit is contained in:
Zahary Karadjov 2018-11-10 02:16:09 +02:00
commit 4728844a32
4 changed files with 183 additions and 0 deletions

3
json_serialization.nim Normal file
View File

@ -0,0 +1,3 @@
import json_serialization/[reader, writer]
export reader, writer

22
json_serialization.nimble Normal file
View File

@ -0,0 +1,22 @@
mode = ScriptMode.Verbose
packageName = "json_serialization"
version = "0.1.0"
author = "Status Research & Development GmbH"
description = "JSON serialization without relying on run-time type information"
license = "Apache License 2.0"
skipDirs = @["tests"]
requires "nim >= 0.17.0",
"ranges"
proc configForTests() =
--hints: off
--debuginfo
--path: "."
--run
task test, "run tests":
configForTests()
setCommand "c", "tests/all.nim"

View File

View File

@ -0,0 +1,158 @@
import
typetraits, serialization/streams
type
JsonWriterState = enum
RecordExpected
RecordStarted
AfterField
JsonWriter*[Stream] = object
stream*: Stream
hasTypeAnnotations: bool
hasPrettyOutput*: bool # read-only
nestingLevel*: int # read-only
state: JsonWriterState
StringJsonWriter* = JsonWriter[StringStream]
proc init*(T: type JsonWriter, pretty = false, typeAnnotations = false): T =
init result.stream
result.hasPrettyOutput = pretty
result.hasTypeAnnotations = typeAnnotations
result.nestingLevel = if pretty: 0 else: -1
result.state = RecordExpected
proc writeImpl(w: var JsonWriter, value: auto)
template writeValue*(w: var JsonWriter, v: auto) =
writeImpl(w, v)
proc beginRecord*(w: var JsonWriter, T: type)
proc beginRecord*(w: var JsonWriter)
template indent(w: var JsonWriter) =
for i in 0 ..< w.nestingLevel:
w.stream.append ' '
proc writeFieldName*(w: var JsonWriter, name: string) =
# this is implemented as a separate proc in order to
# keep the code bloat from `writeField` to a minimum
assert w.state != RecordExpected
if w.state == AfterField:
w.stream.append ','
if w.hasPrettyOutput:
w.stream.append '\n'
w.indent()
w.stream.append '"'
w.stream.append name
w.stream.append '"'
w.stream.append ':'
if w.hasPrettyOutput: w.stream.append ' '
proc writeField*(w: var JsonWriter, name: string, value: auto) =
mixin writeValue
w.writeFieldName(name)
w.state = RecordExpected
w.writeValue(value)
w.state = AfterField
proc beginRecord*(w: var JsonWriter) =
assert w.state == RecordExpected
w.stream.append '{'
if w.hasPrettyOutput:
w.nestingLevel += 2
w.state = RecordStarted
proc beginRecord*(w: var JsonWriter, T: type) =
w.beginRecord()
if w.hasTypeAnnotations: w.writeField("$type", typetraits.name(T))
proc endRecord*(w: var JsonWriter) =
assert w.state != RecordExpected
if w.hasPrettyOutput:
w.stream.append '\n'
w.nestingLevel -= 2
w.indent()
w.stream.append '}'
w.state = RecordExpected
proc writeArray[T](w: var JsonWriter, elements: openarray[T]) =
mixin writeValue
w.stream.append '['
for i, e in elements:
if i != 0: w.stream.append ','
w.state = RecordExpected
w.writeValue(e)
w.stream.append ']'
proc writeImpl(w: var JsonWriter, value: auto) =
template addChar(c) =
w.stream.append c
when value is string:
addChar '"'
template addPrefixSlash(c) =
addChar '\\'
addChar c
for c in value:
case c
of '\L': addPrefixSlash 'n'
of '\b': addPrefixSlash 'b'
of '\f': addPrefixSlash 'f'
of '\t': addPrefixSlash 't'
of '\r': addPrefixSlash 'r'
of '"' : addPrefixSlash '\"'
of '\0'..'\7':
w.stream.append "\\u000"
w.stream.append char(ord('0') + ord(c))
of '\14'..'\31':
w.stream.append "\\u00"
# TODO: Should this really use a decimal representation?
# Or perhaps $ord(c) returns hex?
# This is potentially a bug in Nim's json module.
# In any case, we can call appendNumber here.
w.stream.append $ord(c)
of '\\': addPrefixSlash '\\'
else: addChar c
addChar '"'
elif value is bool:
w.stream.append if value: "true" else: "false"
elif value is enum:
w.stream.appendNumber ord(value)
elif value is SomeInteger:
w.stream.appendNumber value
elif value is SomeFloat:
w.stream.append $value
elif value is (seq or array):
w.writeArray(value)
elif value is (object or tuple):
w.beginRecord(type(value))
# TODO: this won't handle case objects
# introduce and use `value.deserializePairs(k, v):`
for k, v in value.fieldPairs:
w.writeField(k, v)
w.endRecord()
else:
const typeName = typetraits.name(value.type)
{.error: "Failed to convert to JSON an unsupported type: " & typeName.}
template getOutput*(w: JsonWriter): auto =
w.stream.getOutput