Compare commits

...

67 Commits

Author SHA1 Message Date
codex-storage[bot]
649ae60e05
docs: update CHANGELOG.md for master [skip ci] 2025-12-16 06:10:17 +00:00
Arnaud
ca4457a8b0
chore: rename Codex to Logos Storage (#34)
Rename Codex to Logos Storage
2025-12-16 07:09:50 +01:00
codex-storage[bot]
1c0f313662
docs: update CHANGELOG.md for master [skip ci] 2025-10-28 22:24:05 +00:00
markspanbroek
60fcbdab3c
Update chronicles (#33)
* chore: allow for newer versions of chronicles

Signed-off-by: Mark Spanbroek <mark@spanbroek.net>

* chore: add nimble lock

Signed-off-by: Mark Spanbroek <mark@spanbroek.net>

* chore!: no longer support nim 1.6.x

Signed-off-by: Mark Spanbroek <mark@spanbroek.net>

* fix: make tests work with nimble lock file

Signed-off-by: Mark Spanbroek <mark@spanbroek.net>

---------

Signed-off-by: Mark Spanbroek <mark@spanbroek.net>
2025-10-29 09:23:36 +11:00
codex-storage[bot]
5ced7c88b9
docs: update CHANGELOG.md for master [skip ci] 2025-02-19 23:31:17 +00:00
Dmitriy Ryajov
1ce89b206e
fix loglevel and add log scope to allow filtering and avoid log pollution (#30)
fix loglevel and add log scope to avoid log pollution
2025-02-20 10:30:47 +11:00
codex-storage[bot]
69a7a0111a
docs: update CHANGELOG.md for master [skip ci] 2024-10-25 06:54:10 +00:00
Slava
7f9622fa5c
ci: add matrix status job (#29)
adds matrix status job to decouple branch rules status check from a jobs names which are based on Nim version
2024-10-25 17:53:40 +11:00
codex-storage[bot]
fc4ef51b81
docs: update CHANGELOG.md for v1.2.3 [skip ci] 2024-10-23 06:31:21 +00:00
codex-storage[bot]
83e4a2ccf6
docs: update CHANGELOG.md for master [skip ci] 2024-10-23 06:20:37 +00:00
Eric
b2f4dbcd25
v1.2.2 (#28) 2024-10-23 17:20:04 +11:00
codex-storage[bot]
f5af686a85
docs: update CHANGELOG.md for master [skip ci] 2024-10-23 05:53:57 +00:00
Eric
69c630212c
chore: bumps nim from 1.6.16 to 1.6.20 in ci (#27)
bumps nim from 1.6.16 to 1.6.20 in ci
2024-10-23 16:53:28 +11:00
codex-storage[bot]
9ec0e0d5a6
docs: update CHANGELOG.md for master [skip ci] 2024-10-23 05:33:08 +00:00
Eric
c81b751602
chore: remove unneeded echos (#26)
Remove unneeded echos
2024-10-23 16:32:41 +11:00
codex-storage[bot]
cf59b42ed5
docs: update CHANGELOG.md for v1.2.1 [skip ci] 2024-05-21 02:47:23 +00:00
codex-storage[bot]
b39497be32
docs: update CHANGELOG.md for master [skip ci] 2024-05-21 02:46:16 +00:00
Eric
71f30d4678
v1.2.1 (#25) 2024-05-21 12:45:48 +10:00
codex-storage[bot]
6bf626cab4
docs: update CHANGELOG.md for master [skip ci] 2024-05-21 02:40:12 +00:00
Eric
cdba47becf
fix: force symbol resolution for types that serde de/serializes (#24)
* fix: force symbol resolution for types that serde de/serializes

Force symbols into scope when mixins are used with generic overloads. When mixins are used with generic overloads, the overloaded symbols in scope of the mixin are evaluated from the perspective of the mixin. This creates issues in downstream modules that may inadvertantly dispatch *only* to the symbols in the scope of the mixin, even when the module with the wrong symbol overloads is not imported. By forcing the compiler to use symbols for types handled by serde, we can be sure that these symbols are available to downstream modules. We can also be sure that these `fromJson` symbols can be overloaded where needed.

* remove enum forced scoping

Forcing a scoping for a particular enum type would only resolve that type and not all enum types.

* Add mixin + generic overloads as known issue to README

* try to fix URL reference to deserializer.nim
2024-05-21 12:39:47 +10:00
codex-storage[bot]
7fb79610c4
docs: update CHANGELOG.md for master [skip ci] 2024-05-16 07:58:06 +00:00
Eric
10271bd494
feat: improve deserialization from string (#23)
Improves how types handle deserialization from string, including Option[T], seq[T], and Option[seq[T]]
Empty and null strings are no longer deserialized to 0, instead an error Result is returned
If an error occurs when parsing json, a JsonParseError is returned.
2024-05-16 17:57:42 +10:00
codex-storage[bot]
9dd16685d1
docs: update CHANGELOG.md for master [skip ci] 2024-05-16 05:09:36 +00:00
Eric
6d2fc9406a
feat: improve stint parsing (#22)
- change empty string value to none when optional
- handle null, "null", JNull, seq[stint], seq[?string]
2024-05-16 15:09:01 +10:00
codex-storage[bot]
d84641333a
docs: update CHANGELOG.md for v1.2.0 [skip ci] 2024-05-14 04:56:11 +00:00
codex-storage[bot]
d726f6c6eb
docs: update CHANGELOG.md for master [skip ci] 2024-05-14 04:55:06 +00:00
Eric
b7995afb35
chore: v1.2.0 (#21) 2024-05-14 14:54:37 +10:00
codex-storage[bot]
34e28c33d9
docs: update CHANGELOG.md for master [skip ci] 2024-05-14 04:53:00 +00:00
Eric
bd262054f2
fix: add missing test update (#20) 2024-05-14 14:52:32 +10:00
codex-storage[bot]
f9f4b3b662
docs: update CHANGELOG.md for master [skip ci] 2024-05-14 04:49:43 +00:00
Eric
2fcdc9e9c5
chore: reorganize deserialize tests (#19)
Distribute deserialization tests so they are organized up by type.
2024-05-14 14:49:15 +10:00
codex-storage[bot]
2c0518bed0
docs: update CHANGELOG.md for master [skip ci] 2024-05-14 04:46:08 +00:00
Eric
3957da5b52
fix: UInt256 not correctly deserializing from string (#18)
fix: UInt256 not correctly deserialzing from string

UInt256 was being deserialized as an object when being deserialized from a string (not a JString).
2024-05-14 14:45:42 +10:00
codex-storage[bot]
1cedad7488
docs: update CHANGELOG.md for v1.1.1 [skip ci] 2024-05-14 04:29:09 +00:00
codex-storage[bot]
b955985548
docs: update CHANGELOG.md for master [skip ci] 2024-05-13 05:16:15 +00:00
Eric
414a23b52f
chore: v.1.1.1 (#17) 2024-05-13 15:15:46 +10:00
codex-storage[bot]
419d48c8d3
docs: update CHANGELOG.md for master [skip ci] 2024-05-13 05:09:58 +00:00
Eric
baa847e937
chore[formatting]: update formatting (#16)
update formatting
2024-05-13 15:09:34 +10:00
codex-storage[bot]
cefbc47367
docs: update CHANGELOG.md for master [skip ci] 2024-05-13 05:07:31 +00:00
Eric
fbabe954b4
add empty string test for UInt256 (#15) 2024-05-13 15:07:00 +10:00
codex-storage[bot]
b1e5e5d39a
docs: update CHANGELOG.md for master [skip ci] 2024-04-26 08:25:35 +00:00
Ben Bierens
a4a6b0d949
Fix log topics (#14)
adds serde log topics

moves `logScope` into routine scope due to a bug in chronicles
2024-04-26 18:25:02 +10:00
codex-storage[bot]
0b29da6e98
docs: update CHANGELOG.md for master [skip ci] 2024-02-14 06:25:40 +00:00
Eric
6e79374c87
run changelog workflow on release (#12) 2024-02-14 17:25:18 +11:00
codex-storage[bot]
06b271a727
docs: update CHANGELOG.md for master [skip ci] 2024-02-14 06:09:12 +00:00
Eric
88d00624ed
chore: v1.1.0 (#11) 2024-02-14 17:08:44 +11:00
codex-storage[bot]
a261c3d214
docs: update CHANGELOG.md for master [skip ci] 2024-02-14 04:43:05 +00:00
Eric
e67f7e4b04
deserialize seq[T] and Option[T] from string (#9) 2024-02-14 15:42:42 +11:00
codex-storage[bot]
1af86f84dc
docs: update CHANGELOG.md for master [skip ci] 2024-02-14 04:40:55 +00:00
Eric
a2cca7788b
deserialize non-prefixed stuint (#10) 2024-02-14 15:40:32 +11:00
codex-storage[bot]
956d4a2e57
docs: update CHANGELOG.md for master [skip ci] 2024-02-13 06:43:18 +00:00
Eric
9f36b95ac4
v1.0.0 (#8)
Following server, there are breaking changes, so bump MAJOR
2024-02-13 17:42:51 +11:00
codex-storage[bot]
bd9c3d8340
docs: update CHANGELOG.md for master [skip ci] 2024-02-13 06:38:40 +00:00
Eric
96ae7e130e
fix: change serializer funcs to procs (#7)
* change serializer funcs to procs

`%` could have side effects if called from chronicles

* clean up
2024-02-13 17:38:18 +11:00
codex-storage[bot]
ae23a5f306
docs: update CHANGELOG.md for master [skip ci] 2024-02-13 06:11:18 +00:00
Slava
12b9ab6615
Use token for changelog generator (#6)
ci: use token for changelog generator
2024-02-13 17:10:46 +11:00
codex-storage[bot]
d59075df98
docs: update CHANGELOG.md for master [skip ci] 2024-02-13 05:29:16 +00:00
Slava
14492e31ea
Adjust workflows for changelog generation (#5) 2024-02-13 12:46:20 +11:00
Eric
f70e14024e
ci: another attempt to get generated changelog to write to repo 2024-02-10 08:49:59 +11:00
Eric
fe0e21c5a6
ci: change github token secret, and workflow name 2024-02-10 08:42:59 +11:00
Eric
a71a7c39cd
ci: add write permissions to changelog workflow 2024-02-10 08:32:29 +11:00
Eric
2d6ead6245
ci: update CHANGELOG on master push 2024-02-09 16:08:23 +11:00
Eric
b04435fb88
Change parseJson to JsonNode.parse (#4)
* Change parseJson to JsonNode.parse

Exporting `parseJson` causes symbol clashes in downstream repos that import std/json, so changing the signature completely avoid this clash.

* Fix usages of parseJson, update README
2024-02-09 11:08:14 +11:00
Eric
6bd69489a7
give action a name 2024-02-09 10:44:31 +11:00
Eric
42b7d91eb6
Add CI workflow (#3) 2024-02-09 10:37:23 +11:00
Eric
1b77afcbf1
Fix deserialization of openArray[byte] (#2) 2024-02-09 10:33:14 +11:00
Eric
d113c1e158
update changelog 2024-02-08 17:03:24 +11:00
21 changed files with 1107 additions and 236 deletions

41
.github/workflows/changelog.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: changelog
on:
push:
branches:
- master
release:
types: [published]
jobs:
changelog:
name: Generate changelog
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Create Token
id: create_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v4
- name: "✏️ Generate release changelog"
uses: heinrichreimer/action-github-changelog-generator@v2.3
with:
token: ${{ steps.create_token.outputs.token }}
- name: Commit CHANGELOG.md
uses: planetscale/ghcommit-action@v0.1.33
with:
repo: ${{ github.repository }}
branch: master
commit_message: 'docs: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]'
file_pattern: CHANGELOG.md
env:
GITHUB_TOKEN: ${{ steps.create_token.outputs.token }}

36
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
test:
name: Run tests
runs-on: ubuntu-latest
strategy:
matrix:
nim: [2.0.16, 2.2.4]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nim
uses: iffy/install-nim@v4
with:
version: ${{ matrix.nim }}
- name: Build
run: nimble install -y
- name: Test
run: nimble test -y
status:
if: always()
needs: [test]
runs-on: ubuntu-latest
steps:
- if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
run: exit 1

3
.gitignore vendored
View File

@ -5,4 +5,5 @@ nimble.develop
nimble.paths
.idea
vendor/
.vscode/
.vscode/
nimbledeps

View File

@ -1,6 +1,96 @@
## v0.1.1
Add `toJson(pretty=true)`, so that stringified json can be prettified 😊
Formatted all nim files using [`nph`](https://github.com/arnetheduck/nph)
# Changelog
## v0.1.0
Initial versioned release
## [Unreleased](https://github.com/logos-storage/nim-serde/tree/HEAD)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v.1.2.2...HEAD)
**Merged pull requests:**
- chore: rename Codex to Logos Storage [\#34](https://github.com/logos-storage/nim-serde/pull/34) ([2-towns](https://github.com/2-towns))
- Update chronicles [\#33](https://github.com/logos-storage/nim-serde/pull/33) ([markspanbroek](https://github.com/markspanbroek))
- fix loglevel and add log scope to allow filtering and avoid log pollution [\#30](https://github.com/logos-storage/nim-serde/pull/30) ([dryajov](https://github.com/dryajov))
- ci: add matrix status job [\#29](https://github.com/logos-storage/nim-serde/pull/29) ([veaceslavdoina](https://github.com/veaceslavdoina))
## [v.1.2.2](https://github.com/logos-storage/nim-serde/tree/v.1.2.2) (2024-10-23)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.2.1...v.1.2.2)
**Merged pull requests:**
- v1.2.2 [\#28](https://github.com/logos-storage/nim-serde/pull/28) ([emizzle](https://github.com/emizzle))
- chore: bumps nim from 1.6.16 to 1.6.20 in ci [\#27](https://github.com/logos-storage/nim-serde/pull/27) ([emizzle](https://github.com/emizzle))
- chore: remove unneeded echos [\#26](https://github.com/logos-storage/nim-serde/pull/26) ([emizzle](https://github.com/emizzle))
## [v1.2.1](https://github.com/logos-storage/nim-serde/tree/v1.2.1) (2024-05-21)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.2.0...v1.2.1)
**Merged pull requests:**
- v1.2.1 [\#25](https://github.com/logos-storage/nim-serde/pull/25) ([emizzle](https://github.com/emizzle))
- fix: force symbol resolution for types that serde de/serializes [\#24](https://github.com/logos-storage/nim-serde/pull/24) ([emizzle](https://github.com/emizzle))
- feat: improve deserialization from string [\#23](https://github.com/logos-storage/nim-serde/pull/23) ([emizzle](https://github.com/emizzle))
- feat: improve stint parsing [\#22](https://github.com/logos-storage/nim-serde/pull/22) ([emizzle](https://github.com/emizzle))
## [v1.2.0](https://github.com/logos-storage/nim-serde/tree/v1.2.0) (2024-05-14)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.1.1...v1.2.0)
**Merged pull requests:**
- chore: v1.2.0 [\#21](https://github.com/logos-storage/nim-serde/pull/21) ([emizzle](https://github.com/emizzle))
- fix: add missing test update [\#20](https://github.com/logos-storage/nim-serde/pull/20) ([emizzle](https://github.com/emizzle))
- chore: reorganize deserialize tests [\#19](https://github.com/logos-storage/nim-serde/pull/19) ([emizzle](https://github.com/emizzle))
- fix: UInt256 not correctly deserializing from string [\#18](https://github.com/logos-storage/nim-serde/pull/18) ([emizzle](https://github.com/emizzle))
## [v1.1.1](https://github.com/logos-storage/nim-serde/tree/v1.1.1) (2024-05-13)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.1.0...v1.1.1)
**Merged pull requests:**
- chore: v.1.1.1 [\#17](https://github.com/logos-storage/nim-serde/pull/17) ([emizzle](https://github.com/emizzle))
- chore\[formatting\]: update formatting [\#16](https://github.com/logos-storage/nim-serde/pull/16) ([emizzle](https://github.com/emizzle))
- add empty string test for UInt256 [\#15](https://github.com/logos-storage/nim-serde/pull/15) ([emizzle](https://github.com/emizzle))
- Fix log topics [\#14](https://github.com/logos-storage/nim-serde/pull/14) ([benbierens](https://github.com/benbierens))
- run changelog workflow on release [\#12](https://github.com/logos-storage/nim-serde/pull/12) ([emizzle](https://github.com/emizzle))
## [v1.1.0](https://github.com/logos-storage/nim-serde/tree/v1.1.0) (2024-02-14)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v1.0.0...v1.1.0)
**Merged pull requests:**
- chore: v1.1.0 [\#11](https://github.com/logos-storage/nim-serde/pull/11) ([emizzle](https://github.com/emizzle))
- deserialize non-prefixed stuint [\#10](https://github.com/logos-storage/nim-serde/pull/10) ([emizzle](https://github.com/emizzle))
- deserialize seq\[T\] and Option\[T\] from string [\#9](https://github.com/logos-storage/nim-serde/pull/9) ([emizzle](https://github.com/emizzle))
## [v1.0.0](https://github.com/logos-storage/nim-serde/tree/v1.0.0) (2024-02-13)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v0.1.2...v1.0.0)
**Merged pull requests:**
- v1.0.0 [\#8](https://github.com/logos-storage/nim-serde/pull/8) ([emizzle](https://github.com/emizzle))
- fix: change serializer funcs to procs [\#7](https://github.com/logos-storage/nim-serde/pull/7) ([emizzle](https://github.com/emizzle))
- Use token for changelog generator [\#6](https://github.com/logos-storage/nim-serde/pull/6) ([veaceslavdoina](https://github.com/veaceslavdoina))
- Adjust workflows for changelog generation [\#5](https://github.com/logos-storage/nim-serde/pull/5) ([veaceslavdoina](https://github.com/veaceslavdoina))
- Change parseJson to JsonNode.parse [\#4](https://github.com/logos-storage/nim-serde/pull/4) ([emizzle](https://github.com/emizzle))
- Add CI workflow [\#3](https://github.com/logos-storage/nim-serde/pull/3) ([emizzle](https://github.com/emizzle))
- Fix deserialization of openArray\[byte\] [\#2](https://github.com/logos-storage/nim-serde/pull/2) ([emizzle](https://github.com/emizzle))
## [v0.1.2](https://github.com/logos-storage/nim-serde/tree/v0.1.2) (2024-02-08)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v0.1.1...v0.1.2)
## [v0.1.1](https://github.com/logos-storage/nim-serde/tree/v0.1.1) (2024-02-07)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/v0.1.0...v0.1.1)
## [v0.1.0](https://github.com/logos-storage/nim-serde/tree/v0.1.0) (2024-02-07)
[Full Changelog](https://github.com/logos-storage/nim-serde/compare/5a8e85449d9320d2277bc9aadf1daae61c7f057b...v0.1.0)
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*

View File

@ -1,4 +1,4 @@
Copyright (c) 2024 Codex Storage
Copyright (c) 2025 Logos Storage
Licensed and distributed under either of
[MIT license](http://opensource.org/licenses/MIT) or

View File

@ -44,7 +44,7 @@ let jsn2 = """{
}"""
assert !MyType2.fromJson(jsn2) == MyType2(field1: false, field2: true)
# Note, the ! operator is part of https://github.com/codex-storage/questionable, which retrieves a value if set
# Note, the ! operator is part of https://github.com/logos-storage/questionable, which retrieves a value if set
```
Serialize all fields of a type (OptOut mode):
@ -108,7 +108,10 @@ OptOut
Strict
```
Modes can be set in the `{.serialize.}` and/or `{.deserialize.}` pragmas on type definitions. Each mode has a different meaning depending on if the type is being serialized or deserialized. Modes can be set by setting `mode` in the `serialize` or `deserialize` pragma annotation, eg:
Modes can be set in the `{.serialize.}` and/or `{.deserialize.}` pragmas on type
definitions. Each mode has a different meaning depending on if the type is being
serialized or deserialized. Modes can be set by setting `mode` in the `serialize` or
`deserialize` pragma annotation, eg:
```nim
type MyType {.serialize(mode=Strict).} = object
@ -126,7 +129,11 @@ type MyType {.serialize(mode=Strict).} = object
## Default modes
`nim-serde` will de/serialize types if they are not annotated with `serialize` or `deserialize`, but will assume a default mode. By default, with no pragmas specified, `serde` will always serialize in `OptIn` mode, meaning any fields to b Additionally, if the types are annotated, but a mode is not specified, `serde` will assume a (possibly different) default mode.
`nim-serde` will de/serialize types if they are not annotated with `serialize` or
`deserialize`, but will assume a default mode. By default, with no pragmas specified,
`serde` will always serialize in `OptIn` mode, meaning any fields to b Additionally, if
the types are annotated, but a mode is not specified, `serde` will assume a (possibly
different) default mode.
```nim
# Type is not annotated
@ -152,7 +159,8 @@ type MyObj {.serialize, deserialize.} = object
| Default (pragma, but no mode) | `OptOut` | `OptOut` |
## Serde field options
Type fields can be annotated with `{.serialize.}` and `{.deserialize.}` and properties can be set on these pragmas, determining de/serialization behavior.
Type fields can be annotated with `{.serialize.}` and `{.deserialize.}` and properties
can be set on these pragmas, determining de/serialization behavior.
For example,
@ -192,7 +200,9 @@ assert !Person.fromJson(createResponse) == Person(id: 1)
```
### `key`
Specifying a `key`, will alias the field name. When seriazlizing, json will be written with `key` instead of the field name. When deserializing, the json must contain `key` for the field to be deserialized.
Specifying a `key`, will alias the field name. When seriazlizing, json will be written
with `key` instead of the field name. When deserializing, the json must contain `key`
for the field to be deserialized.
### `ignore`
Specifying `ignore`, will prevent de/serialization on the field.
@ -207,7 +217,8 @@ Specifying `ignore`, will prevent de/serialization on the field.
## Deserialization
`serde` deserializes using `fromJson`, and in all instances returns `Result[T, CatchableError]`, where `T` is the type being deserialized. For example:
`serde` deserializes using `fromJson`, and in all instances returns `Result[T,
CatchableError]`, where `T` is the type being deserialized. For example:
```nim
type MyType = object
@ -266,7 +277,8 @@ func fromJson(_: type Address, json: JsonNode): ?!Address =
## Serializing to string (`toJson`)
`toJson` is a shortcut for serializing an object into its serialized string representation:
`toJson` is a shortcut for serializing an object into its serialized string
representation:
```nim
import pkg/serde/json
@ -289,9 +301,11 @@ return RestApiResponse.response(availability.toJson,
## `std/json` drop-in replacment
`nim-serde` can be used as a drop-in replacement for the [standard library's `json` module](https://nim-lang.org/docs/json.html), with a few notable improvements.
`nim-serde` can be used as a drop-in replacement for the [standard library's `json`
module](https://nim-lang.org/docs/json.html), with a few notable improvements.
Instead of importing `std/json` into your application, `pkg/serde/json` can be imported instead:
Instead of importing `std/json` into your application, `pkg/serde/json` can be imported
instead:
```diff
- import std/json
@ -316,7 +330,8 @@ expected["hello"] = newJString("world")
assert %*{"hello": "world"} == expected
```
As well, serialization of types can be overridden, and serialization of custom types can be introduced. Here, we are overriding the serialization of `int`:
As well, serialization of types can be overridden, and serialization of custom types can
be introduced. Here, we are overriding the serialization of `int`:
```nim
import pkg/serde/json
@ -329,7 +344,8 @@ assert 1.toJson == "2"
## `parseJson` and exception tracking
Unfortunately, `std/json`'s `parseJson` can raise an `Exception`, so proper exception tracking breaks, eg
Unfortunately, `std/json`'s `parseJson` can raise an `Exception`, so proper exception
tracking breaks, eg
```nim
@ -352,7 +368,10 @@ proc parseMe(me: string): JsonNode =
assert """{"hello":"world"}""".parseMe == %* { "hello": "world" }
```
This is due to `std/json`'s `parseJson` incorrectly raising `Exception`. This can be worked around by instead importing `serde` and calling its `parseJson`. Note that `serde`'s `parseJson` returns a `Result[JsonNode, CatchableError]` instead of just a plain `JsonNode` object:
This is due to `std/json`'s `parseJson` incorrectly raising `Exception`. This can be
worked around by instead importing `serde` and calling its `JsonNode.parse` routine.
Note that `serde`'s `JsonNode.parse` returns a `Result[JsonNode, CatchableError]`
instead of just a plain `JsonNode` object as in `std/json`'s `parseJson`:
```nim
import pkg/serde/json
@ -363,9 +382,31 @@ type
MyAppError = object of CatchableError
proc parseMe(me: string): JsonNode {.raises: [MyAppError].} =
without parsed =? me.parseJson, error:
without parsed =? JsonNode.parse(me), error:
raise newException(MyAppError, error.msg)
parsed
assert """{"hello":"world"}""".parseMe == %* { "hello": "world" }
```
## Known issues
There is a known issue when using mixins with generic overloaded procs like
`fromJson`. At the time of mixin call, only the `fromJson` overloads in scope of
the called mixin are available to be dispatched at runtime. There could be other
`fromJson` overloads declared in other modules, but are not in scope at the time
the mixin was called. Therefore, anytime `fromJson` is called targeting a
declared overload, it may or may not be dispatchable. This can be worked around
by forcing the `fromJson` overload into scope at compile time. For example, in
your application where the `fromJson` overload is defined, at the bottom of the
module add:
```nim
static: MyType.fromJson("")
```
This will ensure that the `MyType.fromJson` overload is dispatchable.
The basic types that serde supports should already have their overloads forced
in scope in [the `deserializer`
module](./serde/json/deserializer.nim#L340-L356).
For an illustration of the problem, please see this [narrow example](https://github.com/gmega/serialization-bug/tree/main/narrow) by [@gmega](https://github.com/gmega).

View File

@ -1,7 +1,8 @@
--styleCheck:usages
--styleCheck:error
# begin Nimble config (version 1)
when fileExists("nimble.paths"):
# begin Nimble config (version 2)
--noNimblePath
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths"
# end Nimble config

130
nimble.lock Normal file
View File

@ -0,0 +1,130 @@
{
"version": 2,
"packages": {
"results": {
"version": "0.5.1",
"vcsRevision": "df8113dda4c2d74d460a8fa98252b0b771bf1f27",
"url": "https://github.com/arnetheduck/nim-results",
"downloadMethod": "git",
"dependencies": [],
"checksums": {
"sha1": "a9c011f74bc9ed5c91103917b9f382b12e82a9e7"
}
},
"unittest2": {
"version": "0.2.4",
"vcsRevision": "8b51e99b4a57fcfb31689230e75595f024543024",
"url": "https://github.com/status-im/nim-unittest2",
"downloadMethod": "git",
"dependencies": [],
"checksums": {
"sha1": "746106a4dfefffce497f1693733f1c1513b5c62c"
}
},
"stew": {
"version": "0.4.1",
"vcsRevision": "e5740014961438610d336cd81706582dbf2c96f0",
"url": "https://github.com/status-im/nim-stew",
"downloadMethod": "git",
"dependencies": [
"results",
"unittest2"
],
"checksums": {
"sha1": "996d9c058ee078d0209a5f539424a0235683918c"
}
},
"faststreams": {
"version": "0.4.1",
"vcsRevision": "c3ac3f639ed1d62f59d3077d376a29c63ac9750c",
"url": "https://github.com/status-im/nim-faststreams",
"downloadMethod": "git",
"dependencies": [
"stew",
"unittest2"
],
"checksums": {
"sha1": "e2d03280cf0675314136e6e63d9d928d9dfc9705"
}
},
"serialization": {
"version": "0.4.9",
"vcsRevision": "b5193007a49639b21b2b80cd8ef8cbe0df6b0e48",
"url": "https://github.com/status-im/nim-serialization",
"downloadMethod": "git",
"dependencies": [
"faststreams",
"unittest2",
"stew"
],
"checksums": {
"sha1": "ab0812a7e16ae39ee68f4a6b1938a1b83dee34be"
}
},
"json_serialization": {
"version": "0.4.2",
"vcsRevision": "e076cb9a2dff30e13aad0bb4a13049da86d07967",
"url": "https://github.com/status-im/nim-json-serialization",
"downloadMethod": "git",
"dependencies": [
"serialization",
"stew",
"results"
],
"checksums": {
"sha1": "2b26a9e0fc79638dbb9272fb4ab5a1d79264f938"
}
},
"testutils": {
"version": "0.8.0",
"vcsRevision": "e4d37dc1652d5c63afb89907efb5a5e812261797",
"url": "https://github.com/status-im/nim-testutils",
"downloadMethod": "git",
"dependencies": [
"unittest2"
],
"checksums": {
"sha1": "d1678f50aa47d113b4e77d41eec2190830b523fa"
}
},
"chronicles": {
"version": "0.12.2",
"vcsRevision": "27ec507429a4eb81edc20f28292ee8ec420be05b",
"url": "https://github.com/status-im/nim-chronicles",
"downloadMethod": "git",
"dependencies": [
"faststreams",
"serialization",
"json_serialization",
"testutils"
],
"checksums": {
"sha1": "02febb20d088120b2836d3306cfa21f434f88f65"
}
},
"questionable": {
"version": "0.10.15",
"vcsRevision": "82d90b67bcfb7f2e918b61dace2ff1a4ced60935",
"url": "https://github.com/logos-storage/questionable",
"downloadMethod": "git",
"dependencies": [],
"checksums": {
"sha1": "3238ff637c7b44d2fa8fcb839a8ded968e389de3"
}
},
"stint": {
"version": "0.8.2",
"vcsRevision": "470b7892561b5179ab20bd389a69217d6213fe58",
"url": "https://github.com/status-im/nim-stint",
"downloadMethod": "git",
"dependencies": [
"stew",
"unittest2"
],
"checksums": {
"sha1": "d8f871fd617e7857192d4609fe003b48942a8ae5"
}
}
},
"tasks": {}
}

View File

@ -1,20 +1,14 @@
# Package
version = "0.1.2"
author = "nim-serde authors"
description = "Easy-to-use serialization capabilities (currently json only), with a drop-in replacement for std/json."
license = "MIT"
skipDirs = @["tests"]
version = "1.2.2"
author = "nim-serde authors"
description = "Easy-to-use serialization capabilities (currently json only)."
license = "MIT"
skipDirs = @["tests"]
# Dependencies
requires "nim >= 1.6.14"
requires "chronicles >= 0.10.3 & < 0.11.0"
requires "chronicles >= 0.10.3"
requires "questionable >= 0.10.13 & < 0.11.0"
requires "stint"
requires "stew"
task test, "Run the test suite":
exec "nimble install -d -y"
withDir "tests":
exec "nimble test"
requires "asynctest >= 0.5.1 & < 0.6.0"

View File

@ -16,6 +16,7 @@ import ./errors
import ./stdjson
import ./pragmas
import ./types
import ./helpers
export parser
export chronicles except toJson
@ -28,7 +29,7 @@ export types
{.push raises: [].}
logScope:
topics = "json deserialization"
topics = "nimserde json deserializer"
template expectJsonKind(
expectedType: type, expectedKinds: set[JsonNodeKind], json: JsonNode
@ -81,6 +82,12 @@ proc fromJson*[T: SomeInteger](_: type T, json: JsonNode): ?!T =
expectJsonKind(T, {JInt, JString}, json)
case json.kind
of JString:
if json.isNullString:
let err = newSerdeError("Cannot deserialize 'null' into type " & $T)
return failure(err)
elif json.isEmptyString:
return success T(0)
without x =? parseBiggestUInt(json.str).catch, error:
return failure newSerdeError(error.msg)
return success cast[T](x)
@ -126,22 +133,35 @@ proc fromJson*[N: static[int], T: array[N, byte]](_: type T, json: JsonNode): ?!
proc fromJson*[T: distinct](_: type T, json: JsonNode): ?!T =
success T(?T.distinctBase.fromJson(json))
proc fromJson*[N: static[int], T: StUint[N]](_: type T, json: JsonNode): ?!T =
expectJsonKind(T, JString, json)
let jsonStr = json.getStr
let prefix = jsonStr[0 .. 1].toLowerAscii
case prefix
of "0x":
catch parse(jsonStr, T, 16)
of "0o":
catch parse(jsonStr, T, 8)
of "0b":
catch parse(jsonStr, T, 2)
else:
catch parse(jsonStr, T)
proc fromJson*(T: typedesc[StUint or StInt], json: JsonNode): ?!T =
expectJsonKind(T, {JString, JInt}, json)
case json.kind
of JInt:
return catch parse($json, T)
else: # JString (only other kind allowed)
if json.isNullString:
let err = newSerdeError("Cannot deserialize 'null' into type " & $T)
return failure(err)
let jsonStr = json.getStr
let prefix =
if jsonStr.len >= 2:
jsonStr[0 .. 1].toLowerAscii
else:
jsonStr
case prefix
of "0x":
catch parse(jsonStr, T, 16)
of "0o":
catch parse(jsonStr, T, 8)
of "0b":
catch parse(jsonStr, T, 2)
else:
catch parse(jsonStr, T)
proc fromJson*[T](_: type Option[T], json: JsonNode): ?!Option[T] =
if json.isNil or json.kind == JNull:
if json.isNil or json.kind == JNull or json.isEmptyString or json.isNullString:
return success(none T)
without val =? T.fromJson(json), error:
return failure(error)
@ -187,6 +207,10 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
let isOptionalValue = typeof(value) is Option
var skip = false # workaround for 'continue' not supported in a 'fields' loop
# logScope moved into proc due to chronicles issue: https://github.com/status-im/nim-chronicles/issues/148
logScope:
topics = "serde json deserialization"
case mode
of Strict:
if opts.key notin json:
@ -196,16 +220,16 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
warn "object field marked as 'ignore' while in Strict mode, field will be deserialized anyway"
of OptIn:
if not hasDeserializePragma:
debug "object field not marked as 'deserialize', skipping"
trace "object field not marked as 'deserialize', skipping"
skip = true
elif opts.ignore:
debug "object field marked as 'ignore', skipping"
trace "object field marked as 'ignore', skipping"
skip = true
elif opts.key notin json and not isOptionalValue:
return failure newSerdeError("object field missing in json: " & opts.key)
of OptOut:
if opts.ignore:
debug "object field is opted out of deserialization ('ignore' is set), skipping"
trace "object field is opted out of deserialization ('ignore' is set), skipping"
skip = true
elif hasDeserializePragma and opts.key == name:
warn "object field marked as deserialize in OptOut mode, but 'ignore' not set, field will be deserialized"
@ -214,7 +238,7 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
if isOptionalValue:
let jsonVal = json{opts.key}
without parsed =? typeof(value).fromJson(jsonVal), e:
debug "failed to deserialize field",
trace "failed to deserialize field",
`type` = $typeof(value), json = jsonVal, error = e.msg
return failure(e)
value = parsed
@ -222,20 +246,111 @@ proc fromJson*[T: ref object or object](_: type T, json: JsonNode): ?!T =
# not Option[T]
elif opts.key in json and jsonVal =? json{opts.key}.catch and not jsonVal.isNil:
without parsed =? typeof(value).fromJson(jsonVal), e:
debug "failed to deserialize field",
trace "failed to deserialize field",
`type` = $typeof(value), json = jsonVal, error = e.msg
return failure(e)
value = parsed
success(res)
proc fromJson*[T: ref object or object](_: type T, bytes: seq[byte]): ?!T =
let json = ?parse(string.fromBytes(bytes))
proc fromJson*(_: type JsonNode, json: string): ?!JsonNode =
return JsonNode.parse(json)
proc fromJson*[T: ref object or object](_: type T, bytes: openArray[byte]): ?!T =
let json = string.fromBytes(bytes)
T.fromJson(json)
proc fromJson*(_: type JsonNode, jsn: string): ?!JsonNode =
return parser.parseJson(jsn)
proc fromJson*[T: ref object or object](_: type T, jsn: string): ?!T =
let jsn = ?parser.parseJson(jsn) # full qualification required in-module only
proc fromJson*[T: ref object or object](_: type T, json: string): ?!T =
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
T.fromJson(jsn)
proc fromJson*[T: enum](_: type T, json: string): ?!T =
T.fromJson(newJString(json))
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool](
_: type T, json: string
): ?!T =
if json == "" or json == "null":
let err = newSerdeError("Cannot deserialize '' or 'null' into type " & $T)
failure err
else:
let jsn = ?JsonNode.parse(json)
T.fromJson(jsn)
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
_: type Option[T], json: string
): ?!Option[T] =
if json == "" or json == "null":
success T.none
else:
when T is enum:
let jsn = newJString(json)
else:
let jsn = ?JsonNode.parse(json)
Option[T].fromJson(jsn)
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
_: type seq[T], json: string
): ?!seq[T] =
if json == "" or json == "null":
success newSeq[T]()
else:
if T is enum:
let err = newSerdeError("Cannot deserialize a seq[enum]: not yet implemented, PRs welcome")
return failure err
let jsn = ?JsonNode.parse(json)
seq[T].fromJson(jsn)
proc fromJson*[T: SomeInteger or SomeFloat or openArray[byte] or bool or enum](
_: type ?seq[T], json: string
): ?!Option[seq[T]] =
if json == "" or json == "null":
success seq[T].none
else:
if T is enum:
let err = newSerdeError("Cannot deserialize a seq[enum]: not yet implemented, PRs welcome")
return failure err
let jsn = ?JsonNode.parse(json)
Option[seq[T]].fromJson(jsn)
proc fromJson*[T: ref object or object](_: type seq[T], json: string): ?!seq[T] =
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
seq[T].fromJson(jsn)
proc fromJson*(T: typedesc[StUint or StInt], json: string): ?!T =
T.fromJson(newJString(json))
proc fromJson*[T: ref object or object](_: type ?T, json: string): ?!Option[T] =
when T is (StUint or StInt):
let jsn = newJString(json)
else:
let jsn = ?JsonNode.parse(json) # full qualification required in-module only
Option[T].fromJson(jsn)
# Force symbols into scope when mixins are used with generic overloads. When
# mixins are used with generic overloads, the overloaded symbols in scope of the
# mixin are evaluated from the perspective of the mixin. This creates issues in
# downstream modules that may inadvertantly dispatch *only* to the symbols in
# the scope of the mixin, even when the module with the wrong symbol overloads
# is not imported. By forcing the compiler to use symbols for types handled by
# serde, we can be sure that these symbols are available to downstream modules.
# We can also be sure that these `fromJson` symbols can be overloaded where
# needed.
static:
discard bool.fromJson("")
discard Option[bool].fromJson("")
discard seq[bool].fromJson("")
discard uint.fromJson("")
discard Option[uint].fromJson("")
discard seq[uint].fromJson("")
discard int.fromJson("")
discard Option[int].fromJson("")
discard seq[int].fromJson("")
discard UInt256.fromJson("")
discard Option[UInt256].fromJson("")
discard seq[UInt256].fromJson("")
discard JsonNode.fromJson("")
type DistinctType = distinct int
discard DistinctType.fromJson(newJString(""))
discard UInt256.fromJson(newSeq[byte](0))

7
serde/json/helpers.nim Normal file
View File

@ -0,0 +1,7 @@
import std/json
func isEmptyString*(json: JsonNode): bool =
return json.kind == JString and json.getStr == ""
func isNullString*(json: JsonNode): bool =
return json.kind == JString and json.getStr == "null"

View File

@ -2,13 +2,17 @@ import std/json as stdjson
import pkg/questionable/results
import ./errors
import ./types
{.push raises: [].}
proc parseJson*(json: string): ?!JsonNode =
## fix for nim raising Exception
proc parse*(_: type JsonNode, json: string): ?!JsonNode =
# Used as a replacement for `std/json.parseJson`. Will not raise Exception like in the
# standard library
try:
return stdjson.parseJson(json).catch
without val =? stdjson.parseJson(json).catch, error:
return failure error.mapErrTo(JsonParseError)
return success val
except Exception as e:
return failure newException(JsonParseError, e.msg, e)

View File

@ -21,30 +21,30 @@ export types
{.push raises: [].}
logScope:
topics = "json serialization"
topics = "nimserde json serializer"
func `%`*(s: string): JsonNode =
proc `%`*(s: string): JsonNode =
newJString(s)
func `%`*(n: uint): JsonNode =
proc `%`*(n: uint): JsonNode =
if n > cast[uint](int.high):
newJString($n)
else:
newJInt(BiggestInt(n))
func `%`*(n: int): JsonNode =
proc `%`*(n: int): JsonNode =
newJInt(n)
func `%`*(n: BiggestUInt): JsonNode =
proc `%`*(n: BiggestUInt): JsonNode =
if n > cast[BiggestUInt](BiggestInt.high):
newJString($n)
else:
newJInt(BiggestInt(n))
func `%`*(n: BiggestInt): JsonNode =
proc `%`*(n: BiggestInt): JsonNode =
newJInt(n)
func `%`*(n: float): JsonNode =
proc `%`*(n: float): JsonNode =
if n != n:
newJString("nan")
elif n == Inf:
@ -54,10 +54,10 @@ func `%`*(n: float): JsonNode =
else:
newJFloat(n)
func `%`*(b: bool): JsonNode =
proc `%`*(b: bool): JsonNode =
newJBool(b)
func `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
if keyVals.len == 0:
return newJArray()
let jObj = newJObject()
@ -68,13 +68,13 @@ func `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
template `%`*(j: JsonNode): JsonNode =
j
func `%`*[T](table: Table[string, T] | OrderedTable[string, T]): JsonNode =
proc `%`*[T](table: Table[string, T] | OrderedTable[string, T]): JsonNode =
let jObj = newJObject()
for k, v in table:
jObj[k] = ? %v
jObj
func `%`*[T](opt: Option[T]): JsonNode =
proc `%`*[T](opt: Option[T]): JsonNode =
if opt.isSome:
%(opt.get)
else:
@ -99,16 +99,20 @@ proc `%`*[T: object or ref object](obj: T): JsonNode =
let hasSerialize = value.hasCustomPragma(serialize)
var skip = false # workaround for 'continue' not supported in a 'fields' loop
# logScope moved into proc due to chronicles issue: https://github.com/status-im/nim-chronicles/issues/148
logScope:
topics = "serde json serialization"
case mode
of OptIn:
if not hasSerialize:
debug "object field not marked with serialize, skipping"
trace "object field not marked with serialize, skipping"
skip = true
elif opts.ignore:
skip = true
of OptOut:
if opts.ignore:
debug "object field opted out of serialization ('ignore' is set), skipping"
trace "object field opted out of serialization ('ignore' is set), skipping"
skip = true
elif hasSerialize and opts.key == name: # all serialize params are default
warn "object field marked as serialize in OptOut mode, but 'ignore' not set, field will be serialized"
@ -125,22 +129,22 @@ proc `%`*[T: object or ref object](obj: T): JsonNode =
proc `%`*(o: enum): JsonNode =
% $o
func `%`*(stint: StInt | StUint): JsonNode =
proc `%`*(stint: StInt | StUint): JsonNode =
%stint.toString
func `%`*(cstr: cstring): JsonNode =
proc `%`*(cstr: cstring): JsonNode =
% $cstr
func `%`*(arr: openArray[byte]): JsonNode =
proc `%`*(arr: openArray[byte]): JsonNode =
%arr.to0xHex
func `%`*[T](elements: openArray[T]): JsonNode =
proc `%`*[T](elements: openArray[T]): JsonNode =
let jObj = newJArray()
for elem in elements:
jObj.add(%elem)
jObj
func `%`*[T: distinct](id: T): JsonNode =
proc `%`*[T: distinct](id: T): JsonNode =
type baseType = T.distinctBase
%baseType(id)

View File

@ -1,5 +1,4 @@
import std/strutils
func flatten*(s: string): string =
s.replace(" ")
.replace("\n")
s.replace(" ").replace("\n")

7
tests/json.nim Normal file
View File

@ -0,0 +1,7 @@
import ./json/testDeserialize
import ./json/testDeserializeModes
import ./json/testPragmas
import ./json/testSerialize
import ./json/testSerializeModes
{.warning[UnusedImport]: off.}

View File

@ -0,0 +1,209 @@
import std/unittest
import pkg/serde
import pkg/questionable
import pkg/questionable/results
import pkg/stew/byteutils
import pkg/stint
suite "json - deserialize objects":
test "can deserialize json objects":
type MyObj = object
mystring: string
myint: int
myoption: ?bool
let expected = MyObj(mystring: "abc", myint: 123, myoption: some true)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"myint": 123,
"myoption": true
}"""
)
check !MyObj.fromJson(json) == expected
test "ignores serialize pragma when deserializing":
type MyObj = object
mystring {.serialize.}: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: true)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"mybool": true
}"""
)
check !MyObj.fromJson(json) == expected
test "deserializes objects with extra fields":
type MyObj = object
mystring: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: true)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"mybool": true,
"extra": "extra"
}"""
)
check !MyObj.fromJson(json) == expected
test "deserializes objects with less fields":
type MyObj = object
mystring: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: false)
let json =
!JsonNode.parse(
"""{
"mystring": "abc"
}"""
)
check !MyObj.fromJson(json) == expected
test "deserializes ref objects":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let json =
!JsonNode.parse(
"""{
"mystring": "abc",
"myint": 1
}"""
)
let deserialized = !MyRef.fromJson(json)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
test "deserializes openArray[byte]":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let byteArray = """{
"mystring": "abc",
"myint": 1
}""".toBytes
let deserialized = !MyRef.fromJson(byteArray)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
suite "json - deserialize objects from string":
test "deserializes objects from string":
type MyObj = object
mystring: string
myint: int
let expected = MyObj(mystring: "abc", myint: 1)
let myObjJson =
"""{
"mystring": "abc",
"myint": 1
}"""
check !MyObj.fromJson(myObjJson) == expected
test "deserializes ref objects from string":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let myRefJson =
"""{
"mystring": "abc",
"myint": 1
}"""
let deserialized = !MyRef.fromJson(myRefJson)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
test "deserializes seq of objects from string":
type MyObj = object
mystring: string
myint: int
let expected = @[MyObj(mystring: "abc", myint: 1)]
let myObjsJson =
"""[{
"mystring": "abc",
"myint": 1
}]"""
check !seq[MyObj].fromJson(myObjsJson) == expected
test "deserializes Option of object from string":
type MyObj = object
mystring: string
myint: int
let expected = some MyObj(mystring: "abc", myint: 1)
let myObjJson =
"""{
"mystring": "abc",
"myint": 1
}"""
check !(Option[MyObj].fromJson(myObjJson)) == expected
test "deserializes object with UInt256 from string":
type MyObj = object
mystring: string
myu256: UInt256
let expected = MyObj(mystring: "abc", myu256: 1.u256)
let myObjJson =
"""{
"mystring": "abc",
"myu256": 1
}"""
check !MyObj.fromJson(myObjJson) == expected
test "deserializes object with stringified UInt256 from string":
type MyObj = object
mystring: string
myu256: UInt256
let expected = MyObj(mystring: "abc", myu256: 1.u256)
let myObjJson =
"""{
"mystring": "abc",
"myu256": "1"
}"""
check !MyObj.fromJson(myObjJson) == expected
test "deserializes object with ?UInt256 from string":
type MyObj = object
mystring: string
myu256: ?UInt256
let expected = MyObj(mystring: "abc", myu256: UInt256.none)
let myObjJson =
"""{
"mystring": "abc",
}"""
check !MyObj.fromJson(myObjJson) == expected

View File

@ -0,0 +1,217 @@
import std/math
import std/unittest
import pkg/serde
import pkg/questionable
import pkg/questionable/results
suite "json - deserialize std types":
test "deserializes NaN float":
check %NaN == newJString("nan")
test "deserialize enum":
type MyEnum = enum
First
Second
let json = newJString("Second")
check !MyEnum.fromJson(json) == Second
test "deserializes Option[T] when has a value":
let json = newJInt(1)
check (!fromJson(?int, json) == some 1)
test "deserializes Option[T] when has a string value":
check (!fromJson(?int, "1") == some 1)
test "deserializes Option[T] from empty string":
check (!fromJson(?int, "") == int.none)
test "deserializes Option[T] from empty string":
check (!fromJson(?int, "") == int.none)
test "cannot deserialize T from null string":
let res = fromJson(int, "null")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type int"
test "deserializes Option[T] when doesn't have a value":
let json = newJNull()
check !fromJson(?int, json) == none int
test "deserializes float":
let json = newJFloat(1.234)
check !float.fromJson(json) == 1.234
test "deserializes float from string":
check !float.fromJson("1.234") == 1.234
test "cannot deserialize float from empty string":
let res = float.fromJson("")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type float"
test "cannot deserialize float from null string":
let res = float.fromJson("null")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type float"
test "deserializes Inf float":
let json = newJString("inf")
check !float.fromJson(json) == Inf
test "deserializes -Inf float":
let json = newJString("-inf")
check !float.fromJson(json) == -Inf
test "deserializes NaN float":
let json = newJString("nan")
check (!float.fromJson(json)).isNaN
test "deserializes array to sequence":
let expected = @[1, 2, 3]
let json = !JsonNode.parse("[1,2,3]")
check !seq[int].fromJson(json) == expected
test "deserializes uints int.high or smaller":
let largeUInt: uint = uint(int.high)
let json = newJInt(BiggestInt(largeUInt))
check !uint.fromJson(json) == largeUInt
test "deserializes large uints":
let largeUInt: uint = uint(int.high) + 1'u
let json = newJString($BiggestUInt(largeUInt))
check !uint.fromJson(json) == largeUInt
test "deserializes bool from JBool":
let json = newJBool(true)
check !bool.fromJson(json)
test "deserializes bool from string":
check !bool.fromJson("true")
test "cannot deserialize bool from empty string":
let res = bool.fromJson("")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type bool"
test "cannot deserialize bool from null string":
let res = bool.fromJson("null")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize '' or 'null' into type bool"
test "deserializes ?bool from string":
check Option[bool].fromJson("true") == success true.some
test "deserializes ?bool from empty string":
check !Option[bool].fromJson("") == bool.none
test "deserializes ?bool from null string":
check !Option[bool].fromJson("null") == bool.none
test "deserializes seq[bool] from JArray":
let json = newJArray()
json.add(newJBool(true))
json.add(newJBool(false))
check !seq[bool].fromJson(json) == @[true, false]
test "deserializes seq[bool] from string":
check !seq[bool].fromJson("[true, false]") == @[true, false]
test "deserializes seq[bool] from empty string":
check !seq[bool].fromJson("") == newSeq[bool]()
test "deserializes seq[bool] from null string":
check !seq[bool].fromJson("null") == newSeq[bool]()
test "cannot deserialize seq[bool] from unknown string":
let res = seq[bool].fromJson("blah")
check res.error of JsonParseError
check res.error.msg == "input(1, 4) Error: { expected"
test "deserializes ?seq[bool] from string":
check Option[seq[bool]].fromJson("[true, false]") == success @[true, false].some
test "deserializes ?seq[bool] from empty string":
check !Option[seq[bool]].fromJson("") == seq[bool].none
test "deserializes ?seq[bool] from null string":
check !Option[seq[bool]].fromJson("null") == seq[bool].none
test "deserializes enum from JString":
type MyEnum = enum
one
let json = newJString("one")
check !MyEnum.fromJson(json) == MyEnum.one
test "deserializes enum from string":
type MyEnum = enum
one
check !MyEnum.fromJson("one") == MyEnum.one
test "cannot deserialize enum from empty string":
type MyEnum = enum
one
let res = MyEnum.fromJson("")
check res.error of SerdeError
check res.error.msg == "Invalid enum value: "
test "cannot deserialize enum from null string":
type MyEnum = enum
one
let res = MyEnum.fromJson("null")
check res.error of SerdeError
check res.error.msg == "Invalid enum value: null"
test "deserializes ?enum from string":
type MyEnum = enum
one
check Option[MyEnum].fromJson("one") == success MyEnum.one.some
test "deserializes ?enum from empty string":
type MyEnum = enum
one
check !Option[MyEnum].fromJson("") == MyEnum.none
test "deserializes ?enum from null string":
type MyEnum = enum
one
check !Option[MyEnum].fromJson("null") == MyEnum.none
test "deserializes seq[enum] from string":
type MyEnum = enum
one
two
let res = seq[MyEnum].fromJson("[one,two]")
check res.error of SerdeError
check res.error.msg ==
"Cannot deserialize a seq[enum]: not yet implemented, PRs welcome"
test "deserializes ?seq[enum] from string":
type MyEnum = enum
one
two
let res = Option[seq[MyEnum]].fromJson("[one,two]")
check res.error of SerdeError
check res.error.msg ==
"Cannot deserialize a seq[enum]: not yet implemented, PRs welcome"
test "deserializes ?seq[MyEnum] from empty string":
type MyEnum = enum
one
check !Option[seq[MyEnum]].fromJson("") == seq[MyEnum].none
test "deserializes ?seq[MyEnum] from null string":
type MyEnum = enum
one
check !Option[seq[MyEnum]].fromJson("null") == seq[MyEnum].none

View File

@ -0,0 +1,122 @@
import std/options
import std/unittest
import pkg/stint
import pkg/serde
import pkg/questionable
import pkg/questionable/results
suite "json - deserialize stint":
test "deserializes UInt256 from an empty JString":
let json = newJString("")
check !UInt256.fromJson(json) == 0.u256
test "deserializes UInt256 from an empty string":
check !UInt256.fromJson("") == 0.u256
test "deserializes UInt256 from null string":
let res = UInt256.fromJson("null")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize 'null' into type UInt256"
test "deserializes UInt256 from JNull":
let res = UInt256.fromJson(newJNull())
check res.error of UnexpectedKindError
check res.error.msg ==
"deserialization to UInt256 failed: expected {JInt, JString} but got JNull"
test "deserializes ?UInt256 from an empty JString":
let json = newJString("")
check !Option[UInt256].fromJson(json) == UInt256.none
test "deserializes ?UInt256 from an empty string":
check !Option[UInt256].fromJson("") == UInt256.none
test "deserializes ?UInt256 from null string":
check !Option[UInt256].fromJson("null") == UInt256.none
test "deserializes ?UInt256 from JNull":
check !Option[UInt256].fromJson(newJNull()) == UInt256.none
test "deserializes seq[UInt256] from string":
check seq[UInt256].fromJson("[1,2,3]") == success @[1.u256, 2.u256, 3.u256]
test "deserializes seq[UInt256] from string with empty string item":
check seq[UInt256].fromJson("[1,2,\"\"]") == success @[1.u256, 2.u256, 0.u256]
test "deserializes seq[UInt256] from string with null item":
let res = seq[UInt256].fromJson("[1,2,null]")
check res.error of UnexpectedKindError
check res.error.msg ==
"deserialization to UInt256 failed: expected {JInt, JString} but got JNull"
test "deserializes seq[UInt256] from string with null string item":
let res = seq[UInt256].fromJson("[1,2,\"null\"]")
check res.error of SerdeError
check res.error.msg == "Cannot deserialize 'null' into type UInt256"
test "deserializes seq[?UInt256] from string":
check seq[?UInt256].fromJson("[1,2,3]") ==
success @[1.u256.some, 2.u256.some, 3.u256.some]
test "deserializes seq[?UInt256] from string with empty string item":
check seq[?UInt256].fromJson("[1,2,\"\"]") ==
success @[1.u256.some, 2.u256.some, UInt256.none]
test "deserializes seq[?UInt256] from string with null item":
check seq[?UInt256].fromJson("[1,2,null]") ==
success @[1.u256.some, 2.u256.some, UInt256.none]
test "deserializes seq[?UInt256] from string with null string item":
check seq[?UInt256].fromJson("[1,2,\"null\"]") ==
success @[1.u256.some, 2.u256.some, UInt256.none]
test "deserializes UInt256 from JString with no prefix":
let json = newJString("1")
check !UInt256.fromJson(json) == 1.u256
test "deserializes ?UInt256 from JString with no prefix":
let json = newJString("1")
check !Option[UInt256].fromJson(json) == 1.u256.some
test "deserializes UInt256 from string with no prefix":
check !UInt256.fromJson("1") == 1.u256
test "deserializes ?UInt256 from string with no prefix":
check !Option[UInt256].fromJson("1") == 1.u256.some
test "deserializes UInt256 from hex JString representation":
let json = newJString("0x1")
check !UInt256.fromJson(json) == 0x1.u256
test "deserializes ?UInt256 from hex JString representation":
let json = newJString("0x1")
check !Option[UInt256].fromJson(json) == 0x1.u256.some
test "deserializes ?UInt256 from hex string representation":
check !Option[UInt256].fromJson("0x1") == 0x1.u256.some
test "deserializes UInt256 from octal JString representation":
let json = newJString("0o1")
check !UInt256.fromJson(json) == 0o1.u256
test "deserializes ?UInt256 from octal JString representation":
let json = newJString("0o1")
check !Option[UInt256].fromJson(json) == 0o1.u256.some
test "deserializes ?UInt256 from octal string representation":
check !Option[UInt256].fromJson("0o1") == 0o1.u256.some
test "deserializes UInt256 from binary JString representation":
let json = newJString("0b1")
check !UInt256.fromJson(json) == 0b1.u256
test "deserializes ?UInt256 from binary JString representation":
let json = newJString("0b1")
check !Option[UInt256].fromJson(json) == 0b1.u256.some
test "deserializes ?UInt256 from binary string representation":
check !Option[UInt256].fromJson("0b1") == 0b1.u256.some
test "deserializes Int256 with no prefix":
let json = newJString("1")
check !Int256.fromJson(json) == 1.i256

View File

@ -1,139 +1,3 @@
import std/math
import std/options
import std/unittest
import pkg/stint
import pkg/serde
import pkg/questionable
import pkg/questionable/results
suite "json serialization - deserialize":
test "deserializes NaN float":
check %NaN == newJString("nan")
test "deserialize enum":
type MyEnum = enum
First
Second
let json = newJString("Second")
check !MyEnum.fromJson(json) == Second
test "deserializes UInt256 from non-hex string representation":
let json = newJString("100000")
check !UInt256.fromJson(json) == 100000.u256
test "deserializes Option[T] when has a value":
let json = newJInt(1)
check (!fromJson(?int, json) == some 1)
test "deserializes Option[T] when doesn't have a value":
let json = newJNull()
check !fromJson(?int, json) == none int
test "deserializes float":
let json = newJFloat(1.234)
check !float.fromJson(json) == 1.234
test "deserializes Inf float":
let json = newJString("inf")
check !float.fromJson(json) == Inf
test "deserializes -Inf float":
let json = newJString("-inf")
check !float.fromJson(json) == -Inf
test "deserializes NaN float":
let json = newJString("nan")
check (!float.fromJson(json)).isNaN
test "deserializes array to sequence":
let expected = @[1, 2, 3]
let json = !"[1,2,3]".parseJson
check !seq[int].fromJson(json) == expected
test "deserializes uints int.high or smaller":
let largeUInt: uint = uint(int.high)
let json = newJInt(BiggestInt(largeUInt))
check !uint.fromJson(json) == largeUInt
test "deserializes large uints":
let largeUInt: uint = uint(int.high) + 1'u
let json = newJString($BiggestUInt(largeUInt))
check !uint.fromJson(json) == largeUInt
test "can deserialize json objects":
type MyObj = object
mystring: string
myint: int
myoption: ?bool
let expected = MyObj(mystring: "abc", myint: 123, myoption: some true)
let json =
!"""{
"mystring": "abc",
"myint": 123,
"myoption": true
}""".parseJson
check !MyObj.fromJson(json) == expected
test "ignores serialize pragma when deserializing":
type MyObj = object
mystring {.serialize.}: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: true)
let json =
!"""{
"mystring": "abc",
"mybool": true
}""".parseJson
check !MyObj.fromJson(json) == expected
test "deserializes objects with extra fields":
type MyObj = object
mystring: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: true)
let json =
!"""{
"mystring": "abc",
"mybool": true,
"extra": "extra"
}""".parseJson
check !MyObj.fromJson(json) == expected
test "deserializes objects with less fields":
type MyObj = object
mystring: string
mybool: bool
let expected = MyObj(mystring: "abc", mybool: false)
let json =
!"""{
"mystring": "abc"
}""".parseJson
check !MyObj.fromJson(json) == expected
test "deserializes ref objects":
type MyRef = ref object
mystring: string
myint: int
let expected = MyRef(mystring: "abc", myint: 1)
let json =
!"""{
"mystring": "abc",
"myint": 1
}""".parseJson
let deserialized = !MyRef.fromJson(json)
check deserialized.mystring == expected.mystring
check deserialized.myint == expected.myint
import ./deserialize/objects
import ./deserialize/std
import ./deserialize/stint

View File

@ -4,4 +4,4 @@ import ./json/testPragmas
import ./json/testSerialize
import ./json/testSerializeModes
{.warning[UnusedImport]:off.}
{.warning[UnusedImport]: off.}

View File

@ -1,11 +0,0 @@
version = "0.1.0"
author = "nim serde authors"
description = "tests for nim serde library"
license = "MIT"
requires "asynctest >= 0.5.1 & < 0.6.0"
requires "questionable >= 0.10.13 & < 0.11.0"
task test, "Run the test suite":
exec "nimble install -d -y"
exec "nim c -r test"