JSONy

repository·master·Indexed 43 days ago

https://github.com/treeform/jsony

A high-performance, 'loose' JSON parser and serializer for Nim. It supports automatic snake_case to camelCase conversion, field mismatch handling, and a comprehensive hook system (including newHook, postHook, enumHook, renameHook, parseHook, dumpHook, and skipHook) for custom parsing and serialization logic.

Tokens
2K
Snippets
11
Records
12
Agent score
5%

What's inside JSONy

  1. How JSONy hooks work

    master

    Hooks are the core concept of JSONy, allowing you to customize how JSON is parsed or serialized. To use a hook, you must export it (using *) to the scope where parsing/serialization occurs so the system can locate it.

    Available hook types include:

    • newHook*(): Populate default values before full deserialization.
    • postHook*(): Run code after an object is fully parsed (e.g., for sanitization or derived fields).
    • enumHook*(): Map JSON strings to specific Nim enum variants.
    • renameHook*(): Rename JSON field names at runtime (useful for reserved words like type).
    • parseHook*(): Override default parsing logic for any type (e.g., parsing date strings into DateTime).
    • dumpHook*(): Define custom serialization logic for a type.
    • skipHook*(): Prevent specific fields from being serialized.
  2. Basic JSON serialization and deserialization

    master

    JSONy provides simple methods to convert Nim objects to JSON strings and vice versa. It is designed to be 'loose', meaning it ignores extra fields in the JSON and uses default values for missing fields in the Nim object.

    @[1, 2, 3].toJson() -> "[1,2,3]"
    "[1,2,3]".fromJson(seq[int]) -> @[1, 2, 3]
  3. Use `renameHook*()` to handle reserved JSON field names

    master

    If your JSON contains field names that are reserved words in Nim (like type or class), use renameHook*() to rename them to valid Nim identifiers during parsing.

    type Node = ref object
      kind: string
    
    proc renameHook*(v: var Node, fieldName: var string) =
      if fieldName == "type":
        fieldName = "kind"
    
    var node = """{"type":"root"}""".fromJson(Node)
    doAssert node.kind == "root"
  4. Use `skipHook*()` to exclude fields from serialization

    master

    To prevent sensitive or unnecessary fields from being included in the output JSON, implement a skipHook*(). This hook takes the type and the field name and returns true if the field should be skipped.

    type
      Conn = object
        id: int
      Foo = object
        a: int
        password: string
        b: float
        conn: Conn
    
    proc skipHook*(T: typedesc[Foo], key: static string): bool =
      key in ["password", "conn"]
    
    var v = Foo(a:1, password: "12345", b:0.5, conn: Conn(id: 1))
    let s = v.toJson()
    # Result: "{"a":1,"b":0.5}"
  5. Use `enumHook*()` to map JSON strings to Enums

    master

    Since JSON enum values often don't match Nim's internal enum naming conventions (which often use prefixes), use enumHook*() to perform the mapping.

    type Color2 = enum
      c2Red
      c2Blue
      c2Green
    
    proc enumHook*(s: string, v: var Color2) =
      v = case s:
        of "RED": c2Red
        of "BLUE": c2Blue
        of "GREEN": c2Green
        else: c2Red
    
    doAssert """ "RED" """.fromJson(Color2) == c2Red
  6. Use `parseHook*()` for custom type parsing

    master

    The parseHook*() is the most flexible hook. It allows you to intercept the parsing process for any type. Common use cases include:

    • Parsing DateTime from strings.
    • Transforming JSON objects (where keys are IDs) into Nim sequences of objects containing those IDs.
    • Parsing arbitrary types that JSON doesn't natively support.
    # Example: Parsing DateTime from a string
    import jsony, times
    
    proc parseHook*(s: string, i: var int, v: var DateTime) =
      var str: string
      parseHook(s, i, str)
      v = parse(str, "yyyy-MM-dd hh:mm:ss")
    
    var dt = """ "2020-01-01 00:00:00" """.fromJson(DateTime)
  7. Use `RawJson` for high-performance passthrough

    master

    If you need to handle JSON data without fully parsing it (e.g., to store it in a database or pass it to another API), use the RawJson type. RawJson is a distinct string that prevents the library from walking the JSON tree, significantly increasing speed.

    import jsony
    type
      Message = object
        id: uint64
        data: RawJson
    
    let
      messageData = """{"id":123,"data":{"page":"base64","arr":[1,2,3]}}"""
      message = messageData.fromJson(Message)
    
    # The 'data' field remains an unparsed string
    doAssert message.data.string == """{"page":"base64","arr":[1,2,3]}"""
    
    # You can parse it later when needed
    let payload = message.data.string.fromJson(DataPayload)
  8. Use `postHook*()` for post-parsing logic

    master

    Use postHook*() to execute code after an object has been fully deserialized. This is ideal for calculating derived fields or sanitizing data.

    type Sizer = object
      size: int
      originalSize: int
    
    proc postHook*(v: var Sizer) =
      v.originalSize = v.size
    
    var sizer = """{"size":10}""".fromJson(Sizer)
    doAssert sizer.size == 10
    doAssert sizer.originalSize == 10
  9. Use `dumpHook*()` for custom serialization

    master

    Use dumpHook*() to define how a custom Nim type should be represented as a JSON string. The hook receives the current serialization string s and you append your custom representation to it.

    type Fraction = object
      numerator: int
      denominator: int
    
    proc dumpHook*(s: var string, v: Fraction) =
      ## Output fraction type as a string "x/y".
      s.add '"'
      s.add $v.numerator
      s.add '/'
      s.add $v.denominator
      s.add '"'
    
    var f = Fraction(numerator: 10, denominator: 13)
    let s = f.toJson()
    # Result: "10/13"
  10. Use `newHook*()` to set custom default values

    master

    Use newHook*() to initialize an object's fields with specific values before the main parsing process begins. This is useful when the absence of a JSON field should trigger a value other than the type's standard default.

    type
      Foo5 = object
        visible: string
        id: string
    
    proc newHook*(foo: var Foo5) =
      # Populates the object before its fully deserialized.
      foo.visible = "yes"
    
    var s = """{"id":"123"}"""
    var v = s.fromJson(Foo5)
    doAssert v.id == "123"
    doAssert v.visible == "yes"