All Projects → Zulu-Inuoe → jzon

Zulu-Inuoe / jzon

Licence: MIT license
A correct and safe JSON parser.

Programming Languages

common lisp
692 projects
Batchfile
5799 projects
shell
77523 projects

Projects that are alternatives of or similar to jzon

Lora Serialization
LoraWAN serialization/deserialization library for The Things Network
Stars: ✭ 120 (+53.85%)
Mutual labels:  serialization, serializer, deserialization
Go
A high-performance 100% compatible drop-in replacement of "encoding/json"
Stars: ✭ 10,248 (+13038.46%)
Mutual labels:  serialization, serializer, deserialization
Flatsharp
Fast, idiomatic C# implementation of Flatbuffers
Stars: ✭ 133 (+70.51%)
Mutual labels:  serialization, serializer, deserialization
Java
jsoniter (json-iterator) is fast and flexible JSON parser available in Java and Go
Stars: ✭ 1,308 (+1576.92%)
Mutual labels:  serialization, serializer, deserialization
Deku
Declarative binary reading and writing: bit-level, symmetric, serialization/deserialization
Stars: ✭ 136 (+74.36%)
Mutual labels:  serialization, deserialization, encoder-decoder
tyson
A TypeScript serialization/deserialization library to convert objects to/from JSON
Stars: ✭ 25 (-67.95%)
Mutual labels:  serialization, deserialization
parco
🏇🏻 generalist, fast and tiny binary parser and compiler generator, powered by Go 1.18+ Generics
Stars: ✭ 57 (-26.92%)
Mutual labels:  serialization, deserialization
php-json-api
JSON API transformer outputting valid (PSR-7) API Responses.
Stars: ✭ 68 (-12.82%)
Mutual labels:  serialization, serializer
CodableWrapper
@codec("encoder", "decoder") var cool: Bool = true
Stars: ✭ 143 (+83.33%)
Mutual labels:  serialization, deserialization
cattrs
Complex custom class converters for attrs.
Stars: ✭ 565 (+624.36%)
Mutual labels:  serialization, deserialization
Apex.Serialization
High performance contract-less binary serializer for .NET
Stars: ✭ 82 (+5.13%)
Mutual labels:  serialization, serializer
VSerializer
A library to serialize and deserialize objects with minimum memory usage.
Stars: ✭ 25 (-67.95%)
Mutual labels:  serialization, deserialization
laravel5-hal-json
Laravel 5 HAL+JSON API Transformer Package
Stars: ✭ 15 (-80.77%)
Mutual labels:  serialization, serializer
avrow
Avrow is a pure Rust implementation of the avro specification https://avro.apache.org/docs/current/spec.html with Serde support.
Stars: ✭ 27 (-65.38%)
Mutual labels:  serialization, deserialization
nason
🗜 Ultra tiny serializer / encoder with plugin-support. Useful to build binary files containing images, strings, numbers and more!
Stars: ✭ 30 (-61.54%)
Mutual labels:  serialization, deserialization
NBT
A java implementation of the NBT protocol, including a way to implement custom tags.
Stars: ✭ 128 (+64.1%)
Mutual labels:  serialization, deserialization
EndianBinaryIO
A C# library that can read and write primitives, enums, arrays, and strings to streams and byte arrays with specified endianness, string encoding, and boolean sizes.
Stars: ✭ 20 (-74.36%)
Mutual labels:  serialization, serializer
kafka-protobuf-serde
Serializer/Deserializer for Kafka to serialize/deserialize Protocol Buffers messages
Stars: ✭ 52 (-33.33%)
Mutual labels:  serialization, deserialization
util
封装了一些Java常用的功能
Stars: ✭ 19 (-75.64%)
Mutual labels:  serializer, encoder-decoder
drf-action-serializer
A serializer for the Django Rest Framework that supports per-action serialization of fields.
Stars: ✭ 48 (-38.46%)
Mutual labels:  serialization, serializer

jzon

A correct and safe(er) JSON RFC 8259 parser with batteries-included.

Actions Status

Table of Contents

Usage

Type Mappings

jzon maps types per the following chart:

JSON CL
true symbol t
false symbol nil
null symbol null
number integer or double-float
string simple-string
array simple-vector
object hash-table (equal)

Note the usage of symbol cl:null as a sentinel for JSON null

Reading

There's a single entry point: parse:

(jzon:parse "
{
  \"name\": \"Rock\",
  \"coords\": {
    \"map\": \"stony_hill\",
    \"x\": 5,
    \"y\": 10
  },
  \"attributes\": [\"fast\", \"hot\"],
  \"physics\": true,
  \"item\": false,
  \"parent\": null
}")

parse reads input from its single argument and returns a parsed value per Type Mappings.

in can be any of the following:

  • string
  • (vector (unsigned-byte 8)) - octets in utf-8
  • stream - character or binary in utf-8
  • pathname - parse will open the file for reading

parse also accepts the follwing keyword arguments:

  • :allow-comments This allows the given JSON to contain //cpp-style comments
  • :max-depth This controls the maximum depth to allow arrays/objects to nest. Can be a positive integer, or nil to disable depth tests.
  • :max-string-length This controls the maximum length of strings. This applies for both keys and values. Must be a positive integer no larger than array-dimension-limit.
  • :key-fn A function of one argument responsible for 'interning' object keys. Should accept a simple-string and return the 'interned' key

Tip: key-fn can be supplied as #'identity in order to disable key pooling:

(parse "[ { \"x\": 1, \"y\": 1 }, { \"x\": 1, \"y\": 1 } ]" :key-fn #'identity)

Tip: alexandria:make-keyword or equivalent can be used to make object keys into symbols:

(jzon:parse "[ { \"x\": 1, \"y\": 1 }, { \"x\": 1, \"y\": 1 } ]" :key-fn #'alexandria:make-keyword)

Writing

stringify will serialize an object to JSON:

(stringify #("Hello, world!" 5 2.2 #(null)))
; => "[\"Hello, world!\",5,2.2,[null]]"

stringify accepts the following keyword arguments:

  • :stream A destination like in format, or a pathname. Like format, returns a string if nil.
  • :pretty If true, output pretty-formatted JSON
  • :coerce-element A function for coercing 'non-native' values to JSON. See Custom Serialization
  • :coerce-key A function for coercing key values to strings. See Custom Serialization

In addition to the mappings defined in Type Mappings, stringify accepts the following types of values:

CL JSON
symbol string (symbol-name), but see Symbol key case
real number
alist* object
plist* object
list array
sequence array
standard-object object
structure-object† object

*: Heuristic depending on the key values - Detects alists/plists by testing each key to be a character, string, or symbol.

†: On supported implementations where structure slots are available via the MOP.

These coercion rules only apply when using the default :coerce-element and :coerce-key.

Symbol key case

When symbols are used as keys in objects, their names will be downcased, unless they contain mixed-case characters.

For example:

(let ((ht (make-hash-table)))
  (setf (gethash 'all-upper ht) 0)
  (setf (gethash '|mixedCase| ht) 0)

  (stringify ht))

shall result in:

{
  "all-upper": 0,
  "mixedCase": 0
}

This is particularly important when serializing CLOS objects per Custom Serialization.

Custom Serialization

stringify allows serializing any values not covered in the Type Mappings in an few different ways.

By default, if your object is a standard-object, it will be serialized as a JSON object, using each of its bound slots as keys.

Consider the following classes:

(defclass coordinate ()
  ((reference
    :initarg :reference)
   (x
    :initform 0
    :initarg :x
    :accessor x)
   (y
    :initform 0
    :initarg :y
    :accessor y)))

(defclass object ()
  ((alive
    :initform nil
    :initarg :alive
    :type boolean)
   (coordinate
    :initform nil
    :initarg :coordinate
    :type (or null coordinate))
   (children
    :initform nil
    :initarg :children
    :type list)))

If we stringify a fresh coordinate object via (stringify (make-instance 'coordinate)), we'd end up with:

{
  "x": 0,
  "y": 0
}

And if we (stringify (make-instance 'coordinate :reference "Earth")):

{
  "reference": "Earth",
  "x": 0,
  "y": 0
}

Similarly if we (stringify (make-instance 'object)):

{
  "alive": false,
  "coordinate": null,
  "children": []
}

Note that here we have nil representing false, null, and []. This is done by examining the :type of each slot. If no type is provided, nil shall serialize as null.

stringify recurses, so if we have:

(stringify (make-instance 'object :coordinate (make-instance 'coordinate)))

We'll have:

{
  "alive": false,
  "coordinate": {
    "x": 0,
    "y": 0
  },
  "children": []
}

coerced-fields

If you wish more control over how your object is serialized, the most straightforward way is to specialize coerced-fields.

Consider our previous coordinate class. If we always wanted to serialize only the x and y slots, and wanted to rename them, we could specialize coerced-fields as follows:

(defmethod coerced-fields ((coordinate coordinate))
  (list (list "coord-x" (x coordinate))
        (list "coord-y" (y coordinate))))

This results in:

{
  "coord-x": 0,
  "coord-y": 0
}

coerced-fields should a list of 'fields', which are two (or three) element lists of the form:

(name value &optional type)

The name can be any suitable key name. In particular, integers are allowed coerced to their decimal string representation.

The value can be any value - it'll be coerced if necessary.

The type is used as :type above, in order to resolve ambiguities with nil.

Including only some slots

If the default coerced-fields gives you most of what you want, you can exclude/rename/add fields by specializing an :around method as follows:

(defmethod coerced-fields :around ((coordinate coordinate))
  (let* (;; Grab default fields
         (fields (call-next-method))
         ;; All fields except "children"
         (fields (remove 'children fields :key #'first))
         ;; Include a 'fake' field "name"
         (fields (cons (list 'name "Mary") fields)))
    fields))

This would result in the following:

{
  "name": "Mary",
  "alive": false,
  "coordinate": {
    "x": 0,
    "y": 0
  }
}

write-value

For more fine-grained control, you can specialize a method on jzon:write-value.

jzon:write-value writer value

writer is a json-writer on which any of the writer functions may be called to serialize your object in any desired way.

(defclass my-point () ())

(defmethod jzon:write-value (writer (value my-point))
  (jzon:write-array writer 1 2))

See json-writer for the available functions.

json-writer

In addition to stringify, jzon also provides an imperative, streaming writer for writing JSON.

The following are the available functions for writing:

General

  • jzon:write-value - Writes any value to the writer. Usable when writing a toplevel value, object property value, or array element.

Object

  • jzon:with-object
  • jzon:begin-object
  • jzon:write-key
  • json:write-property
  • json:write-properties
  • json:end-object
  • jzon:write-object

Array

  • jzon:with-array
  • jzon:begin-array
  • jzon:write-values
  • json:end-array
  • jzon:write-array

Example

(let ((writer (jzon:make-json-writer :stream *standard-output* :pretty t)))
  (jzon:with-object writer
    (jzon:write-properties writer :age 24 "colour" "blue")
    (jzon:write-key writer 42)
    (jzon:write-value writer #(1 2 3))

    (jzon:write-key writer "an-array")
    (jzon:with-array writer
      (jzon:write-values writer :these :are :array :elements))

    (jzon:write-key writer "another array")
    (jzon:write-array writer :or "you" "can" "use these" "helpers")))

produces:

{
  "age": 24,
  "colour": "blue",
  "42": [
    1,
    2,
    3
  ],
  "an-array": [
    "THESE",
    "ARE",
    "ARRAY",
    "ELEMENTS"
  ],
  "another array": [
    "OR",
    "you",
    "can",
    "use these",
    "helpers"
  ]
}

It's worth noting that every function returns the writer itself for usage with arrow macros:

(let ((writer (jzon:make-json-writer :stream *standard-output*)))
  (jzon:with-object writer
    (-> writer
        (jzon:write-key "key")
        (jzon:write-value "value")
        (jzon:begin-array)
        (jzon:write-value 1)
        (jzon:end-array))))`

Features

This section notes some of jzon's more noteworthy features.

In general, jzon strives for (in order):

  • Safety
  • Correctness
  • Simplicity
  • Interoperability
  • Performance

Unambiguous values

Values are never ambiguous between [], false, {}, null, or a missing key, as in some other json parsers.

Strict spec compliance

This parser is written against RFC 8259 and strives to adhere strictly for maximum compliance and little surprises.

Also, this has been tested against the JSONTestSuite. See the JSONTestSuite directory in this repo for making & running the tests.

Safety

RFC 8259 allows setting limits on things such as:

  • Number values accepted
  • Nesting level of arrays/objects
  • Length of strings

jzon is meant to be safe in the face of untrusted JSON and will error on otherwise 'reasonable' input out-of-the-box.

jzon's parse is also type-safe, and shall not, for example:

CL-USER> (parse 2)
; Debugger entered on #<SB-SYS:MEMORY-FAULT-ERROR {1003964833}>

.. as in some other libraries.

jzon also chooses to (by default) keep object keys as strings. This is done rather than using symbols via intern because over time, symbols will continue to be allocated and because they are in a package, will not be collected by the garbage collector, causing a memory leak.

Simplicity

You call parse, and you get a reasonable standard CL object back.

  • No custom data structures or accessors required
  • No worrying about key case auto conversion or hyphens/underscores being converted.
  • No worrying about what package symbols are interned in (no symbols).
  • No worrying about dynamic variables affecting a parse as in cl-json, jonathan, jsown. Everything affecting parse is given at the call-site.

parse also accepts either a string, octet vector, stream, or pathname for simpler usage over libraries requiring one or the other, or having separate parse functions.

Object key pooling

jzon will use a key pool per-parse, causing shared keys in a nested JSON object to share keys:

(parse "[{\"x\": 5}, {\"x\": 10}, {\"x\": 15}]")

In this example, the string x is shared (eq) between all 3 objects.

This optimizes for the common case of reading a JSON payload containing many duplicate keys.

Dependencies

Alternatives

There are many CL JSON libraries available, and I defer to Sabra Crolleton's definitive list and comparisons https://sabracrolleton.github.io/json-review.

But for posterity, included in this repository is a set of tests and results for the following libraries:

  • cl-json
  • jonathan
  • json-streams
  • jsown
  • yason

No ill-will is meant for these other libraries. I simply want jzon to be better and become a true de-facto library in the world of JSON-in-cl once and for all.

License

See LICENSE.

jzon was originally a fork of st-json, but I ended up scrapping all of the code except for for the function decoding Unicode.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].