jsony
SkillDev toolsFast JSON parsing and serialization for Nim with automatic derived hooks
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the jsony skill
What this skill tells your AI
The instructions your AI receives, as published by mratsim/tattletale in .agents/skills/jsony/SKILL.md and read by ahel’s review.
What I do
jsony is a high-performance JSON library for Nim that provides:
- Automatic derivation of JSON parsers/serializers for custom types
- Customizable hooks for fine-grained control (
parseHook,dumpHook,renameHook, etc.) - Zero-copy parsing where possible
- No external dependencies
When to use me
Use jsony when you need to:
- Parse JSON into Nim types (objects, seqs, arrays, tables, enums, tuples)
- Serialize Nim types to JSON
- Handle nested or complex JSON structures
- Customize field names, default values, or validation
Basic usage
Parsing JSON into types
import pkg/jsony
type MyObject = object
name: string
value: int
let json_str = """{"name": "test", "value": 42}"""
let obj = json_str.fromJson(MyObject)
Serializing types to JSON
let obj = MyObject(name: "test", value: 42)
let json_str = obj.toJson()
Supported types
- seq[T] - Parses JSON arrays into sequences
- array[N, T] - Fixed-size arrays
- object - JSON objects map to object fields
- ref object - Pointer types
- enum - String or integer enum values
- tuple - Named or unnamed tuples
- Table[K, V] / OrderedTable - JSON objects
- set[T] / HashSet - JSON arrays
- Option[T] - Nullable values (null maps to
none) - bool, int, float, string, char - Primitives
- JsonNode - Raw JSON parsing
Custom hooks for fine-grained control
renameHook - Map JSON keys to different field names
proc renameHook*(v: var MyObject, key: string) =
if key == "my_name":
key = "name"
enumHook - Custom enum parsing
proc enumHook*(strVal: string, v: var MyEnum) =
if strVal == "ACTIVE":
v = MyEnum.active
newHook - Initialize fields with defaults
proc newHook*(v: var MyObject) =
v.value = 100 # Default value
postHook - Validate after parsing
proc postHook*(v: var MyObject) =
if v.value < 0:
raise newException(ValueError, "Value must be positive")
skipHook - Skip specific fields during parsing
proc skipHook*(T: typedesc[MyObject], key: string): bool =
key == "internal_field" # Skip this field
Example from Constantine tests
From t_ethereum_bls_signatures.nim and t_ethereum_evm_precompiles.nim:
import pkg/jsony
type
PubkeyField = object
pubkey: array[48, byte]
DeserG1_test = object
input: PubkeyField
output: bool
# Custom parsing for byte arrays from hex strings
proc parseHook*[N: static int](src: string, pos: var int, value: var array[N, byte]) =
var str: string
parseHook(src, pos, str)
value.paddedFromHex(str, bigEndian)
# Parse test vectors
let testFile = readFile(testDir / filename)
let testData = testFile.fromJson(DeserG1_test)
# Use parsed data
let status = pubkey.deserialize_pubkey_compressed(testData.input.pubkey)
Dealing with extra JSON fields
By default, jsony ignores unknown fields:
fromJson[T]()ignores extra JSON fields- Missing fields keep their default values (use
newHookto set defaults)
Performance tips
- For large JSON, use
parseHookinstead of generic parsing distincttypes automatically derive serialization- Compile with
-d:releasefor optimized parsing
Error handling
try:
let obj = json_str.fromJson(MyObject)
except JsonError as e:
echo "JSON parse error: ", e.msg
Recursive parseHook - Nested complex structures
From t_hash_to_curve.nim and t_ec_sage_template.nim:
type
EC_G1_hex = object
x: string
y: string
EC_G2_hex = object
x: Fp2_hex # Nested object
y: Fp2_hex
Fp2_hex = object
c0: string
c1: string
TestVector[EC: EC_ShortW_Aff] = object
P: EC # Recursive generic type
Q0, Q1: EC
msg: string
u: seq[string]
HashToCurveTest[EC: EC_ShortW_Aff] = object
L, Z, dst: string
curve: string
field: FieldDesc
vectors: seq[TestVector[EC]]
# Recursive parseHook for elliptic curve points
proc parseHook*(src: string, pos: var int, value: var EC_ShortW_Aff) =
when EC_ShortW_Aff.F is Fp:
var P: EC_G1_hex
parseHook(src, pos, P) # Recursive call
let ok = value.fromHex(P.x, P.y)
elif EC_ShortW_Aff.F is Fp2:
var P: EC_G2_hex
parseHook(src, pos, P) # Recursive call
let ok = value.fromHex(P.x.c0, P.x.c1, P.y.c0, P.y.c1)
# Generic bigint parsing
proc parseHook*(src: string, pos: var int, value: var BigInt) =
var str: string
parseHook(src, pos, str) # Parse string first
value.fromHex(str) # Then convert
# Usage with generic test vectors
let vec = loadVectors(HashToCurveTest[EC_ShortW_Aff[Fp[BLS12_381], G1]], filename)
for i in 0 ..< vec.vectors.len:
var P: EC
P.fromAffine(vec.vectors[i].P) # Nested parsing
Working with conditional fields and generics
From t_ec_sage_template.nim:
type
ScalarMulTestG2[EC: EC_ShortW_Aff, bits: static int] = object
curve, group, modulus: string
# Conditional fields based on generic parameter
when EC.F is Fp:
non_residue_twist: int
else:
non_residue_twist: array[2, int]
vectors: seq[TestVector[EC, bits]]
# Load vectors with generic type
proc loadVectors(TestType: typedesc): TestType =
const group = when TestType.EC.G == G1: "G1" else: "G2"
const filename = "tv_" & $TestType.EC.F.Name & "_scalar_mul_" & group & "_" & $TestType.bits & "bit.json"
let content = readFile(TestVectorsDir/filename)
result = content.fromJson(TestType)
# Usage
let vec = loadVectors(ScalarMulTestG2[EC_ShortW_Aff[Fp2[BLS12_381], G2], 256])
Advanced patterns
Static if/when in hooks
proc parseHook*(src: string, pos: var int, value: var MyGeneric[T]) =
when T is SomeNumber:
# Parse numeric type
var num: T
parseHook(src, pos, num)
value = MyGeneric[T](data: num)
elif T is string:
# Parse string type
var str: string
parseHook(src, pos, str)
value = MyGeneric[T](data: str)
Multiple dispatch with static parameters
proc parseHook*[EC: static typedesc](src: string, pos: var int, value: var EC) =
# Static dispatch based on EC type
when EC.G == G1:
var P: EC_G1_hex
parseHook(src, pos, P)
discard value.fromHex(P.x, P.y)
else: # EC.G == G2
var P: EC_G2_hex
parseHook(src, pos, P)
discard value.fromHex(P.x.c0, P.x.c1, P.y.c0, P.y.c1)
Integration with other code
jsony works seamlessly with:
- Standard library types (tables, sets, options)
- Custom serialization libraries (via
parseHook/dumpHook) - Test frameworks for parsing test vectors
- FFI code for parsing C/interop data structures
- Generic parameterized types and static dispatch
Signals
- GitHub stars
- 40
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
jsony- Source
- github.com/mratsim/tattletale