nim-json-serialization/json_serialization/writer.nim

198 lines
4.4 KiB
Nim
Raw Normal View History

2018-11-10 00:16:09 +00:00
import
2018-12-17 23:01:06 +00:00
typetraits,
2019-03-24 23:11:54 +00:00
faststreams/output_stream, serialization, json
2018-11-10 00:16:09 +00:00
type
JsonWriterState = enum
RecordExpected
RecordStarted
AfterField
2018-12-17 23:01:06 +00:00
JsonWriter* = object
2019-03-11 09:39:19 +00:00
stream*: OutputStreamVar
2018-11-10 00:16:09 +00:00
hasTypeAnnotations: bool
hasPrettyOutput*: bool # read-only
nestingLevel*: int # read-only
state: JsonWriterState
2019-03-24 23:11:54 +00:00
JsonString* = distinct string
2019-03-11 09:39:19 +00:00
proc init*(T: type JsonWriter, stream: OutputStreamVar,
2018-12-17 23:01:06 +00:00
pretty = false, typeAnnotations = false): T =
result.stream = stream
2018-11-10 00:16:09 +00:00
result.hasPrettyOutput = pretty
result.hasTypeAnnotations = typeAnnotations
result.nestingLevel = if pretty: 0 else: -1
result.state = RecordExpected
proc beginRecord*(w: var JsonWriter, T: type)
proc beginRecord*(w: var JsonWriter)
2019-03-24 23:11:54 +00:00
proc writeValue*(w: var JsonWriter, value: auto)
2018-11-10 00:16:09 +00:00
2018-12-17 23:01:06 +00:00
template append(x: untyped) =
2019-03-11 09:39:19 +00:00
w.stream.append x
2018-12-17 23:01:06 +00:00
template indent =
2018-11-10 00:16:09 +00:00
for i in 0 ..< w.nestingLevel:
2018-12-17 23:01:06 +00:00
append ' '
2018-11-10 00:16:09 +00:00
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
doAssert w.state != RecordExpected
2018-11-10 00:16:09 +00:00
if w.state == AfterField:
2018-12-17 23:01:06 +00:00
append ','
2018-11-10 00:16:09 +00:00
if w.hasPrettyOutput:
2018-12-17 23:01:06 +00:00
append '\n'
2018-11-10 00:16:09 +00:00
2018-12-17 23:01:06 +00:00
indent()
2018-11-10 00:16:09 +00:00
2018-12-17 23:01:06 +00:00
append '"'
append name
append '"'
append ':'
if w.hasPrettyOutput: append ' '
2018-11-10 00:16:09 +00:00
w.state = RecordExpected
2018-11-10 00:16:09 +00:00
proc writeField*(w: var JsonWriter, name: string, value: auto) =
mixin writeValue
w.writeFieldName(name)
w.writeValue(value)
w.state = AfterField
proc beginRecord*(w: var JsonWriter) =
doAssert w.state == RecordExpected
2018-11-10 00:16:09 +00:00
2018-12-17 23:01:06 +00:00
append '{'
2018-11-10 00:16:09 +00:00
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) =
doAssert w.state != RecordExpected
2018-11-10 00:16:09 +00:00
if w.hasPrettyOutput:
2018-12-17 23:01:06 +00:00
append '\n'
2018-11-10 00:16:09 +00:00
w.nestingLevel -= 2
2018-12-17 23:01:06 +00:00
indent()
2018-11-10 00:16:09 +00:00
2018-12-17 23:01:06 +00:00
append '}'
template endRecordField*(w: var JsonWriter) =
endRecord(w)
w.state = AfterField
2018-11-10 00:16:09 +00:00
2019-07-16 10:20:05 +00:00
proc writeIterable*(w: var JsonWriter, collection: auto) =
2018-11-10 00:16:09 +00:00
mixin writeValue
2018-12-17 23:01:06 +00:00
append '['
if w.hasPrettyOutput:
append '\n'
w.nestingLevel += 2
indent()
2019-07-16 10:20:05 +00:00
var first = true
for e in collection:
if not first:
append ','
if w.hasPrettyOutput:
append '\n'
indent()
2018-11-10 00:16:09 +00:00
w.state = RecordExpected
w.writeValue(e)
2019-07-16 10:20:05 +00:00
first = false
if w.hasPrettyOutput:
append '\n'
w.nestingLevel -= 2
indent()
2018-12-17 23:01:06 +00:00
append ']'
2018-11-10 00:16:09 +00:00
2019-07-16 10:20:05 +00:00
proc writeArray[T](w: var JsonWriter, elements: openarray[T]) =
writeIterable(w, elements)
2019-03-24 23:11:54 +00:00
proc writeValue*(w: var JsonWriter, value: auto) =
mixin enumInstanceSerializedFields
2018-11-10 00:16:09 +00:00
template addChar(c) =
2018-12-17 23:01:06 +00:00
append c
2018-11-10 00:16:09 +00:00
2019-03-24 23:11:54 +00:00
when value is JsonNode:
append if w.hasPrettyOutput: value.pretty
else: $value
elif value is JsonString:
append string(value)
elif value is ref:
if value == nil:
append "null"
else:
writeValue(w, value[])
elif value is string|cstring:
2018-11-10 00:16:09 +00:00
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':
2018-12-17 23:01:06 +00:00
append "\\u000"
append char(ord('0') + ord(c))
2018-11-10 00:16:09 +00:00
of '\14'..'\31':
2018-12-17 23:01:06 +00:00
append "\\u00"
2018-11-10 00:16:09 +00:00
# TODO: Should this really use a decimal representation?
# Or perhaps $ord(c) returns hex?
# This is potentially a bug in Nim's json module.
2018-12-17 23:01:06 +00:00
append $ord(c)
2018-11-10 00:16:09 +00:00
of '\\': addPrefixSlash '\\'
else: addChar c
addChar '"'
elif value is bool:
2018-12-17 23:01:06 +00:00
append if value: "true" else: "false"
2018-11-10 00:16:09 +00:00
elif value is enum:
w.stream.append $ord(value)
2018-11-10 00:16:09 +00:00
elif value is SomeInteger:
w.stream.append $value
2018-11-10 00:16:09 +00:00
elif value is SomeFloat:
2018-12-17 23:01:06 +00:00
append $value
2018-11-10 00:16:09 +00:00
elif value is (seq or array):
w.writeArray(value)
elif value is (object or tuple):
w.beginRecord(type(value))
value.enumInstanceSerializedFields(k, v):
w.writeField k, v
2018-11-10 00:16:09 +00:00
w.endRecord()
else:
const typeName = typetraits.name(value.type)
2019-03-13 21:20:58 +00:00
{.fatal: "Failed to convert to JSON an unsupported type: " & typeName.}
2018-11-10 00:16:09 +00:00
proc toJson*(v: auto, pretty = false, typeAnnotations = false): string =
mixin writeValue
2019-03-11 09:39:19 +00:00
var s = init OutputStream
var w = JsonWriter.init(s, pretty, typeAnnotations)
w.writeValue v
2019-03-11 09:39:19 +00:00
return s.getOutput(string)