mirror of
https://github.com/status-im/nim-json-serialization.git
synced 2025-02-16 12:07:35 +00:00
Improved error handling and pretty-printing for arrays
This commit is contained in:
parent
002c6bbb38
commit
c2daa5a23c
@ -23,7 +23,7 @@ type
|
|||||||
tkColon,
|
tkColon,
|
||||||
tkComma
|
tkComma
|
||||||
|
|
||||||
JsonError* = enum
|
JsonErrorKind* = enum
|
||||||
errNone = "no error",
|
errNone = "no error",
|
||||||
errHexCharExpected = "hex char expected (part of escape sequence)",
|
errHexCharExpected = "hex char expected (part of escape sequence)",
|
||||||
errStringExpected = "string expected",
|
errStringExpected = "string expected",
|
||||||
@ -48,7 +48,7 @@ type
|
|||||||
tokenStart: int
|
tokenStart: int
|
||||||
|
|
||||||
tok*: TokKind
|
tok*: TokKind
|
||||||
err*: JsonError
|
err*: JsonErrorKind
|
||||||
|
|
||||||
intVal*: int64
|
intVal*: int64
|
||||||
floatVal*: float
|
floatVal*: float
|
||||||
@ -91,7 +91,7 @@ proc init*(T: type JsonLexer, stream: ref ByteStream, mode = defaultJsonMode): a
|
|||||||
type AS = ref AsciiStream
|
type AS = ref AsciiStream
|
||||||
init(JsonLexer, AS(stream), mode)
|
init(JsonLexer, AS(stream), mode)
|
||||||
|
|
||||||
template error(error: JsonError) {.dirty.} =
|
template error(error: JsonErrorKind) {.dirty.} =
|
||||||
lexer.err = error
|
lexer.err = error
|
||||||
lexer.tok = tkError
|
lexer.tok = tkError
|
||||||
return
|
return
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
import
|
import
|
||||||
strutils, typetraits, macros,
|
strutils, typetraits, macros, strformat,
|
||||||
faststreams/input_stream, serialization/object_serialization,
|
faststreams/input_stream, serialization/[object_serialization, errors],
|
||||||
types, lexer
|
types, lexer
|
||||||
|
|
||||||
export
|
export
|
||||||
types
|
types, errors
|
||||||
|
|
||||||
type
|
type
|
||||||
JsonReader* = object
|
JsonReader* = object
|
||||||
lexer: JsonLexer
|
lexer: JsonLexer
|
||||||
|
|
||||||
JsonReaderError* = object of CatchableError
|
JsonReaderError* = object of JsonError
|
||||||
line*, col*: int
|
line*, col*: int
|
||||||
|
|
||||||
UnexpectedField* = object of JsonReaderError
|
UnexpectedField* = object of JsonReaderError
|
||||||
encounteredField*: cstring
|
encounteredField*: cstring
|
||||||
deserializedType*: cstring
|
deserializedType*: cstring
|
||||||
|
|
||||||
|
CustomSerializationError* = object of JsonReaderError
|
||||||
|
deserializedType*: cstring
|
||||||
|
innerException*: ref CatchableError
|
||||||
|
|
||||||
ExpectedTokenCategory* = enum
|
ExpectedTokenCategory* = enum
|
||||||
etBool = "bool literal"
|
etBool = "bool literal"
|
||||||
etInt = "integer"
|
etInt = "integer"
|
||||||
@ -33,16 +37,21 @@ type
|
|||||||
encountedToken*: TokKind
|
encountedToken*: TokKind
|
||||||
expectedToken*: ExpectedTokenCategory
|
expectedToken*: ExpectedTokenCategory
|
||||||
|
|
||||||
proc init*(T: type JsonReader, stream: AsciiStreamVar, mode = defaultJsonMode): T =
|
method formatMsg*(err: ref JsonReaderError, filename: string): string =
|
||||||
result.lexer = JsonLexer.init(stream, mode)
|
fmt"{filename}({err.line}, {err.col}) Error while reading json file"
|
||||||
result.lexer.next()
|
|
||||||
|
method formatMsg*(err: ref UnexpectedField, filename: string): string =
|
||||||
|
fmt"{filename}({err.line}, {err.col}) Unexpected field '{err.encounteredField}' while deserialing {err.deserializedType}"
|
||||||
|
|
||||||
|
method formatMsg*(err: ref UnexpectedToken, filename: string): string =
|
||||||
|
fmt"{filename}({err.line}, {err.col}) Unexpected token '{err.encountedToken}' in place of '{err.expectedToken}'"
|
||||||
|
|
||||||
|
method formatMsg*(err: ref CustomSerializationError, filename: string): string =
|
||||||
|
fmt"{filename}({err.line}, {err.col}) Error while deserializing '{err.deserializedType}': {err.innerException.msg}"
|
||||||
|
|
||||||
template init*(T: type JsonReader, stream: ByteStreamVar, mode = defaultJsonMode): auto =
|
template init*(T: type JsonReader, stream: ByteStreamVar, mode = defaultJsonMode): auto =
|
||||||
init JsonReader, AsciiStreamVar(stream), mode
|
init JsonReader, AsciiStreamVar(stream), mode
|
||||||
|
|
||||||
proc setParsed[T: enum](e: var T, s: string) =
|
|
||||||
e = parseEnum[T](s)
|
|
||||||
|
|
||||||
proc assignLineNumber(ex: ref JsonReaderError, r: JsonReader) =
|
proc assignLineNumber(ex: ref JsonReaderError, r: JsonReader) =
|
||||||
ex.line = r.lexer.line
|
ex.line = r.lexer.line
|
||||||
ex.col = r.lexer.col
|
ex.col = r.lexer.col
|
||||||
@ -61,6 +70,22 @@ proc raiseUnexpectedField(r: JsonReader, fieldName, deserializedType: cstring) =
|
|||||||
ex.deserializedType = deserializedType
|
ex.deserializedType = deserializedType
|
||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
|
proc raiseCustomSerializationError(r: JsonReader,
|
||||||
|
deserializedType: cstring,
|
||||||
|
innerException: ref CatchableError) =
|
||||||
|
var ex = new CustomSerializationError
|
||||||
|
ex.assignLineNumber(r)
|
||||||
|
ex.deserializedType = deserializedType
|
||||||
|
ex.innerException = innerException
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
proc init*(T: type JsonReader, stream: AsciiStreamVar, mode = defaultJsonMode): T =
|
||||||
|
result.lexer = JsonLexer.init(stream, mode)
|
||||||
|
result.lexer.next()
|
||||||
|
|
||||||
|
proc setParsed[T: enum](e: var T, s: string) =
|
||||||
|
e = parseEnum[T](s)
|
||||||
|
|
||||||
proc requireToken(r: JsonReader, tk: TokKind) =
|
proc requireToken(r: JsonReader, tk: TokKind) =
|
||||||
if r.lexer.tok != tk:
|
if r.lexer.tok != tk:
|
||||||
r.raiseUnexpectedToken case tk
|
r.raiseUnexpectedToken case tk
|
||||||
@ -77,7 +102,31 @@ proc skipToken(r: var JsonReader, tk: TokKind) =
|
|||||||
r.requireToken tk
|
r.requireToken tk
|
||||||
r.lexer.next()
|
r.lexer.next()
|
||||||
|
|
||||||
proc readImpl(r: var JsonReader, value: var auto) =
|
template checkedRead(r: var JsonReader, value: var auto) =
|
||||||
|
# TODO: Here, we are interested in catching errors only from
|
||||||
|
# user-defined overloads of `readValue`. At the moment, this
|
||||||
|
# is a bit hard to do, because we cannot discriminate between
|
||||||
|
# the overload here and the mixed-in ones.
|
||||||
|
# Perhaps a more cleaner implementation will have two separate
|
||||||
|
# overloadable procs:
|
||||||
|
#
|
||||||
|
# * `readValue`
|
||||||
|
# intended for people who implement Readers
|
||||||
|
#
|
||||||
|
# * `customReadValue`
|
||||||
|
# intended for user-defined types,
|
||||||
|
# may be hidden behind a `customSerialization` template
|
||||||
|
|
||||||
|
mixin readValue
|
||||||
|
try:
|
||||||
|
readValue(r, value)
|
||||||
|
except JsonError:
|
||||||
|
raise
|
||||||
|
except CatchableError as err:
|
||||||
|
type T = type(value)
|
||||||
|
raiseCustomSerializationError(r, typetraits.name(T), err)
|
||||||
|
|
||||||
|
proc readValue*(r: var JsonReader, value: var auto) =
|
||||||
mixin readValue
|
mixin readValue
|
||||||
|
|
||||||
let tok = r.lexer.tok
|
let tok = r.lexer.tok
|
||||||
@ -126,7 +175,7 @@ proc readImpl(r: var JsonReader, value: var auto) =
|
|||||||
while true:
|
while true:
|
||||||
let lastPos = value.len
|
let lastPos = value.len
|
||||||
value.setLen(lastPos + 1)
|
value.setLen(lastPos + 1)
|
||||||
readValue(r, value[lastPos])
|
checkedRead(r, value[lastPos])
|
||||||
if r.lexer.tok != tkComma: break
|
if r.lexer.tok != tkComma: break
|
||||||
r.lexer.next()
|
r.lexer.next()
|
||||||
r.skipToken tkBracketRi
|
r.skipToken tkBracketRi
|
||||||
@ -136,9 +185,9 @@ proc readImpl(r: var JsonReader, value: var auto) =
|
|||||||
for i in low(value) ..< high(value):
|
for i in low(value) ..< high(value):
|
||||||
# TODO: dont's ask. this makes the code compile
|
# TODO: dont's ask. this makes the code compile
|
||||||
if false: value[i] = value[i]
|
if false: value[i] = value[i]
|
||||||
readValue(r, value[i])
|
checkedRead(r, value[i])
|
||||||
r.skipToken tkComma
|
r.skipToken tkComma
|
||||||
readValue(r, value[high(value)])
|
checkedRead(r, value[high(value)])
|
||||||
r.skipToken tkBracketRi
|
r.skipToken tkBracketRi
|
||||||
|
|
||||||
elif value is (object or tuple):
|
elif value is (object or tuple):
|
||||||
@ -168,6 +217,3 @@ proc readImpl(r: var JsonReader, value: var auto) =
|
|||||||
const typeName = typetraits.name(value.type)
|
const typeName = typetraits.name(value.type)
|
||||||
{.error: "Failed to convert to JSON an unsupported type: " & typeName.}
|
{.error: "Failed to convert to JSON an unsupported type: " & typeName.}
|
||||||
|
|
||||||
template readValue*(r: var JsonReader, value: var auto) =
|
|
||||||
readImpl(r, value)
|
|
||||||
|
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
|
import
|
||||||
|
serialization/errors
|
||||||
|
|
||||||
|
export
|
||||||
|
errors
|
||||||
|
|
||||||
type
|
type
|
||||||
JsonMode* = enum
|
JsonMode* = enum
|
||||||
Relaxed
|
Relaxed
|
||||||
Portable
|
Portable
|
||||||
|
|
||||||
JsonError* = object of CatchableError
|
JsonError* = object of SerializationError
|
||||||
|
|
||||||
const
|
const
|
||||||
defaultJsonMode* = JsonMode.Relaxed
|
defaultJsonMode* = JsonMode.Relaxed
|
||||||
|
@ -98,10 +98,27 @@ proc writeArray[T](w: var JsonWriter, elements: openarray[T]) =
|
|||||||
mixin writeValue
|
mixin writeValue
|
||||||
|
|
||||||
append '['
|
append '['
|
||||||
|
|
||||||
|
if w.hasPrettyOutput:
|
||||||
|
append '\n'
|
||||||
|
w.nestingLevel += 2
|
||||||
|
indent()
|
||||||
|
|
||||||
for i, e in elements:
|
for i, e in elements:
|
||||||
if i != 0: append ','
|
if i != 0:
|
||||||
|
append ','
|
||||||
|
if w.hasPrettyOutput:
|
||||||
|
append '\n'
|
||||||
|
indent()
|
||||||
|
|
||||||
w.state = RecordExpected
|
w.state = RecordExpected
|
||||||
w.writeValue(e)
|
w.writeValue(e)
|
||||||
|
|
||||||
|
if w.hasPrettyOutput:
|
||||||
|
append '\n'
|
||||||
|
w.nestingLevel -= 2
|
||||||
|
indent()
|
||||||
|
|
||||||
append ']'
|
append ']'
|
||||||
|
|
||||||
proc writeImpl(w: var JsonWriter, value: auto) =
|
proc writeImpl(w: var JsonWriter, value: auto) =
|
||||||
|
@ -22,6 +22,9 @@ type
|
|||||||
z: ref Simple
|
z: ref Simple
|
||||||
# o: Option[Simple]
|
# o: Option[Simple]
|
||||||
|
|
||||||
|
HoldsArray = object
|
||||||
|
data: seq[int]
|
||||||
|
|
||||||
Invalid = object
|
Invalid = object
|
||||||
distance: Mile
|
distance: Mile
|
||||||
|
|
||||||
@ -59,6 +62,21 @@ suite "toJson tests":
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
test "arrays are printed correctly":
|
||||||
|
var x = HoldsArray(data: @[1, 2, 3, 4])
|
||||||
|
|
||||||
|
check:
|
||||||
|
x.toJson(pretty = true) == dedent"""
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
test "max unsigned value":
|
test "max unsigned value":
|
||||||
var uintVal = not uint64(0)
|
var uintVal = not uint64(0)
|
||||||
let jsonValue = Json.encode(uintVal)
|
let jsonValue = Json.encode(uintVal)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user