Improve the marshal module (#16777)

* Improve marshal

Use runnableExamples
Refactor tests

* Readd {.inheritable.} test

Apply suggestions
This commit is contained in:
konsumlamm 2021-01-22 19:52:34 +01:00 • committed by GitHub
commit d2b218b80a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 106 additions and 193 deletions

View file

@ -10,39 +10,32 @@
## This module contains procs for `serialization`:idx: and `deserialization`:idx: ## This module contains procs for `serialization`:idx: and `deserialization`:idx:
## of arbitrary Nim data structures. The serialization format uses `JSON`:idx:. ## of arbitrary Nim data structures. The serialization format uses `JSON`:idx:.
## ##
## **Restriction**: For objects their type is **not** serialized. This means ## **Restriction**: For objects, their type is **not** serialized. This means
## essentially that it does not work if the object has some other runtime ## essentially that it does not work if the object has some other runtime
## type than its compiletime type. ## type than its compiletime type.
## ##
## ##
## Basic usage ## Basic usage
## =========== ## ===========
##
## .. code-block:: nim runnableExamples:
## type
## type A = object of RootObj
## A = object of RootObj B = object of A
## B = object of A f: int
## f: int
## let a: ref A = new(B)
## var assert $$a[] == "{}" # not "{f: 0}"
## a: ref A
## b: ref B # unmarshal
## let c = to[B]("""{"f": 2}""")
## new(b) assert typeof(c) is B
## a = b assert c.f == 2
## echo($$a[]) # produces "{}", not "{f: 0}"
## # marshal
## # unmarshal assert $$c == """{"f": 2}"""
## let c = to[B]("""{"f": 2}""")
## assert typeof(c) is B ## **Note**: The `to` and `$$` operations are available at compile-time!
## assert c.f == 2
##
## # marshal
## let s = $$c
## assert s == """{"f": 2}"""
##
## **Note**: The ``to`` and ``$$`` operations are available at compile-time!
## ##
## ##
## See also ## See also
@ -61,7 +54,7 @@ Please use alternative packages for serialization.
It is possible to reimplement this module using generics and type traits. It is possible to reimplement this module using generics and type traits.
Please contribute a new implementation.""".} Please contribute a new implementation.""".}
import streams, typeinfo, json, intsets, tables, unicode import std/[streams, typeinfo, json, intsets, tables, unicode]
proc ptrToInt(x: pointer): int {.inline.} = proc ptrToInt(x: pointer): int {.inline.} =
result = cast[int](x) # don't skip alignment result = cast[int](x) # don't skip alignment
@ -279,7 +272,8 @@ proc loadAny(s: Stream, a: Any, t: var Table[BiggestInt, pointer]) =
proc load*[T](s: Stream, data: var T) = proc load*[T](s: Stream, data: var T) =
## Loads `data` from the stream `s`. Raises `IOError` in case of an error. ## Loads `data` from the stream `s`. Raises `IOError` in case of an error.
runnableExamples: runnableExamples:
import marshal, streams import std/streams
var s = newStringStream("[1, 3, 5]") var s = newStringStream("[1, 3, 5]")
var a: array[3, int] var a: array[3, int]
load(s, a) load(s, a)
@ -291,7 +285,8 @@ proc load*[T](s: Stream, data: var T) =
proc store*[T](s: Stream, data: T) = proc store*[T](s: Stream, data: T) =
## Stores `data` into the stream `s`. Raises `IOError` in case of an error. ## Stores `data` into the stream `s`. Raises `IOError` in case of an error.
runnableExamples: runnableExamples:
import marshal, streams import std/streams
var s = newStringStream("") var s = newStringStream("")
var a = [1, 3, 5] var a = [1, 3, 5]
store(s, a) store(s, a)
@ -306,7 +301,8 @@ proc store*[T](s: Stream, data: T) =
proc `$$`*[T](x: T): string = proc `$$`*[T](x: T): string =
## Returns a string representation of `x` (serialization, marshalling). ## Returns a string representation of `x` (serialization, marshalling).
## ##
## **Note:** to serialize `x` to JSON use `$(%x)` from the ``json`` module. ## **Note:** to serialize `x` to JSON use `%x` from the `json` module
## or `jsonutils.toJson(x)`.
runnableExamples: runnableExamples:
type type
Foo = object Foo = object
@ -325,7 +321,7 @@ proc `$$`*[T](x: T): string =
result = s.data result = s.data
proc to*[T](data: string): T = proc to*[T](data: string): T =
## Reads data and transforms it to a type ``T`` (deserialization, unmarshalling). ## Reads data and transforms it to a type `T` (deserialization, unmarshalling).
runnableExamples: runnableExamples:
type type
Foo = object Foo = object
@ -341,77 +337,3 @@ proc to*[T](data: string): T =
var tab = initTable[BiggestInt, pointer]() var tab = initTable[BiggestInt, pointer]()
loadAny(newStringStream(data), toAny(result), tab) loadAny(newStringStream(data), toAny(result), tab)
when not defined(testing) and isMainModule:
template testit(x: untyped) = echo($$to[typeof(x)]($$x))
var x: array[0..4, array[0..4, string]] = [
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"]]
testit(x)
var test2: tuple[name: string, s: uint] = ("tuple test", 56u)
testit(test2)
type
TE = enum
blah, blah2
TestObj = object
test, asd: int
case test2: TE
of blah:
help: string
else:
nil
PNode = ref Node
Node = object
next, prev: PNode
data: string
proc buildList(): PNode =
new(result)
new(result.next)
new(result.prev)
result.data = "middle"
result.next.data = "next"
result.prev.data = "prev"
result.next.next = result.prev
result.next.prev = result
result.prev.next = result
result.prev.prev = result.next
var test3: TestObj
test3.test = 42
test3 = TestObj(test2: blah)
testit(test3)
var test4: ref tuple[a, b: string]
new(test4)
test4.a = "ref string test: A"
test4.b = "ref string test: B"
testit(test4)
var test5 = @[(0, 1), (2, 3), (4, 5)]
testit(test5)
var test6: set[char] = {'A'..'Z', '_'}
testit(test6)
var test7 = buildList()
echo($$test7)
testit(test7)
type
A {.inheritable.} = object
B = object of A
f: int
var
a: ref A
b: ref B
new(b)
a = b
echo($$a[]) # produces "{}", not "{f: 0}"

View file

@ -1,32 +1,15 @@
discard """ import std/marshal
output: '''{"age": 12, "bio": "Я Cletus", "blob": [65, 66, 67, 128], "name": "Cletus"}
true
true
alpha 100
omega 200
Some(null)
None[JsonNode]
(numeric: "")
hello world
'''
joinable: false
"""
#[ # TODO: add static tests
joinable: false pending https://github.com/nim-lang/Nim/issues/9754
]#
import marshal proc testit[T](x: T): string = $$to[T]($$x)
template testit(x) = discard $$to[typeof(x)]($$x) let test1: array[0..1, array[0..4, string]] = [
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"]]
var x: array[0..4, array[0..4, string]] = [ doAssert testit(test1) ==
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], """[["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"]]"""
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], let test2: tuple[name: string, s: int] = ("tuple test", 56)
["test", "1", "2", "3", "4"]] doAssert testit(test2) == """{"Field0": "tuple test", "Field1": 56}"""
testit(x)
var test2: tuple[name: string, s: int] = ("tuple test", 56)
testit(test2)
type type
TE = enum TE = enum
@ -57,29 +40,30 @@ proc buildList(): PNode =
result.prev.next = result result.prev.next = result
result.prev.prev = result.next result.prev.prev = result.next
var test3: TestObj let test3 = TestObj(test: 42, test2: blah)
test3.test = 42 doAssert testit(test3) ==
test3.test2 = blah """{"test": 42, "asd": 0, "test2": "blah", "help": ""}"""
testit(test3)
var test4: ref tuple[a, b: string] var test4: ref tuple[a, b: string]
new(test4) new(test4)
test4.a = "ref string test: A" test4.a = "ref string test: A"
test4.b = "ref string test: B" test4.b = "ref string test: B"
testit(test4) discard testit(test4) # serialization uses the pointer address, which is not consistent
var test5 = @[(0,1),(2,3),(4,5)] let test5 = @[(0,1),(2,3),(4,5)]
testit(test5) doAssert testit(test5) ==
"""[{"Field0": 0, "Field1": 1}, {"Field0": 2, "Field1": 3}, {"Field0": 4, "Field1": 5}]"""
var test7 = buildList() let test6: set[char] = {'A'..'Z', '_'}
testit(test7) doAssert testit(test6) ==
"""[65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 95]"""
var test6: set[char] = {'A'..'Z', '_'} let test7 = buildList()
testit(test6) discard testit(test7) # serialization uses the pointer address, which is not consistent
# bug #1352 # bug #1352
block:
type type
Entity = object of RootObj Entity = object of RootObj
name: string name: string
@ -89,32 +73,29 @@ type
bio: string bio: string
blob: string blob: string
var instance1 = Person(name: "Cletus", age: 12, let instance1 = Person(name: "Cletus", age: 12,
bio: "Я Cletus", bio: "Я Cletus",
blob: "ABC\x80") blob: "ABC\x80")
echo($$instance1) doAssert $$instance1 == """{"age": 12, "bio": "Я Cletus", "blob": [65, 66, 67, 128], "name": "Cletus"}"""
echo(to[Person]($$instance1).bio == instance1.bio) # true doAssert to[Person]($$instance1).bio == instance1.bio
echo(to[Person]($$instance1).blob == instance1.blob) # true doAssert to[Person]($$instance1).blob == instance1.blob
# bug 5757
# bug #5757
block:
type type
Something = object Something = object
x: string x: string
y: int y: int
var data1 = """{"x": "alpha", "y": 100}""" let data1 = """{"x": "alpha", "y": 100}"""
var data2 = """{"x": "omega", "y": 200}""" let data2 = """{"x": "omega", "y": 200}"""
var r = to[Something](data1) var r = to[Something](data1)
doAssert $r.x & " " & $r.y == "alpha 100"
echo r.x, " ", r.y
r = to[Something](data2) r = to[Something](data2)
doAssert $r.x & " " & $r.y == "omega 200"
echo r.x, " ", r.y block:
type type
Foo = object Foo = object
a1: string a1: string
@ -126,18 +107,19 @@ type
var foo = Foo(a2: "", a4: @[], a6: @[1]) var foo = Foo(a2: "", a4: @[], a6: @[1])
foo.a6.setLen 0 foo.a6.setLen 0
doAssert $$foo == """{"a1": "", "a2": "", "a3": [], "a4": [], "a5": [], "a6": []}""" doAssert $$foo == """{"a1": "", "a2": "", "a3": [], "a4": [], "a5": [], "a6": []}"""
testit(foo) doAssert testit(foo) == """{"a1": "", "a2": "", "a3": [], "a4": [], "a5": [], "a6": []}"""
import options, json import std/[options, json]
# bug #15934 # bug #15934
block: block:
let let
a1 = some(newJNull()) a1 = some(newJNull())
a2 = none(JsonNode) a2 = none(JsonNode)
echo ($$a1).to[:Option[JsonNode]] doAssert $($$a1).to[:Option[JsonNode]] == "Some(null)"
echo ($$a2).to[:Option[JsonNode]] doAssert $($$a2).to[:Option[JsonNode]] == "None[JsonNode]"
doAssert ($$a1).to[:Option[JsonNode]] == some(newJNull())
doAssert ($$a2).to[:Option[JsonNode]] == none(JsonNode)
# bug #15620 # bug #15620
block: block:
@ -148,10 +130,19 @@ block:
numeric: string numeric: string
let test = to[LegacyEntry](str) let test = to[LegacyEntry](str)
echo test doAssert $test == """(numeric: "")"""
# bug #16022 # bug #16022
block: block:
let p: proc () = proc () = echo "hello world" let p: proc (): string = proc (): string = "hello world"
let poc = (to[typeof(p)]($$p)) let poc = to[typeof(p)]($$p)
poc() doAssert poc() == "hello world"
block:
type
A {.inheritable.} = object
B = object of A
f: int
let a: ref A = new(B)
doAssert $$a[] == "{}" # not "{f: 0}"