trivial-json-codec

2022-07-08

A JSON parser able to identify class hierarchies.

Upstream URL

gitlab.com/ediethelm/trivial-json-codec

Author

Eric Diethelm <ediethelm@yahoo.com>

License

MIT
README

Trivial JSON Codec Manual

[in package TRIVIAL-JSON-CODEC]

pipeline status Quicklisp

Description

A JSON parser able to handle class hierarchies.

Limitations: It expects the classes of the hierarchy to have at least one slot named differently. It does not fully follow the JSON specification.

Lists are serialized different than arrays:

(trivial-json-codec:serialize-json '(1 2 3 4))
=> "<1,2,3,4>"

(trivial-json-codec:serialize-json (make-array (1) :initial-contents '(1 2 3 4)))
=> "[1,2,3,4]"

Installing trivial-json-codec

This project is available in the latest QuickLisp distribution, so installing it is reduced to calling:

(ql:quickload :trivial-json-codec)

Working Example

The following code demonstrates the use-case for this library.

Note: The sequence '=>' indicates the result from evaluating the previous expression.

(defclass Payload ()
  ())

(defclass SimplePayload (Payload)
  ((value :type integer
          :initarg :value)))

(defclass ComplicatedPayload (Payload)
  ((value :type string
          :initarg :value)
   (additional-info :type string
		    :initarg :additional-info)
   (message-id :type trivial-utilities:positive-fixnum
	       :initarg :message-id)))

(defclass DifferentPayload (Payload)
  ((cargo :type fixnum
          :initarg :cargo)))

(defclass Message ()
  ((uid :initarg :uid
	:initform nil
	:accessor uid)
   (payload :type (or null Payload)
	    :initarg :payload
	    :accessor payload)))

(c2mop:ensure-finalized (find-class 'Payload))
(c2mop:ensure-finalized (find-class 'SimplePayload))
(c2mop:ensure-finalized (find-class 'ComplicatedPayload))
(c2mop:ensure-finalized (find-class 'DifferentPayload))
(c2mop:ensure-finalized (find-class 'Message))

(let ((message (make-instance 'Message :uid 1 :payload (make-instance 'Simplepayload :value 12345))))
  (trivial-json-codec:serialize-json message))
=> "{ \"UID\" : 1,  \"PAYLOAD\" : { \"VALUE\" : 12345}}"

(deserialize-json "{ \"UID\" : 1,  \"PAYLOAD\" : { \"VALUE\" : 12345}}" :class (find-class 'Message))
=> #<MESSAGE> with a payload of type SimplePayload


(let ((message (make-instance 'Message :uid 2 :payload (make-instance 'ComplicatedPayload :value "abc" :message-id 17 :additional-info "1234"))))
  (trivial-json-codec:serialize-json message))
=> "{ \"UID\" : 2,  \"PAYLOAD\" : { \"VALUE\" : \"abc\",  \"ADDITIONAL-INFO\" : \"1234\",  \"MESSAGE-ID\" : 17}}"

(deserialize-json "{ \"UID\" : 2,  \"PAYLOAD\" : { \"VALUE\" : \"abc\",  \"ADDITIONAL-INFO\" : \"1234\",  \"MESSAGE-ID\" : 17}}" :class (find-class 'Message))
=> #<MESSAGE> with a payload of type ComplicatedPayload

(let ((message (make-instance 'Message :uid 2 :payload (make-instance 'DifferentPayload :cargo -147))))
  (trivial-json-codec:serialize-json message))
=> "{ \"UID\" : 2,  \"PAYLOAD\" : { \"CARGO\" : -147}}"

(deserialize-json "{ \"UID\" : 2,  \"PAYLOAD\" : { \"CARGO\" : -147}}" :class (find-class 'Message))
=>  #<MESSAGE> with a payload of type DifferentPayload


Due to the known limitation mentioned in the description, the following is NOT possible:

(defclass StringPayload (Payload)
  ((value :type string
          :initarg :value)))

(let ((message (make-instance 'Message :uid 2 :payload (make-instance 'StringPayload :value "abc"))))
  (trivial-json-codec:serialize-json message))
=> "{ \"UID\" : 2,  \"PAYLOAD\" : { \"VALUE" : \"abc\"}}"

(deserialize-json "{ \"UID\" : 2,  \"PAYLOAD\" : { \"VALUE" : \"abc\"}}" :class (find-class 'Message))
=> This terminates with an error due to non-unique class mapping. StringPayload and Simplepayload differ only on the slot's type.

Exported Symbols

  • [function] SERIALIZE-JSON OBJ

    Takes OBJ and serializes it into a string. Uses the generic SERIALIZE to do the job.

  • [function] DESERIALIZE-JSON JSON-STR &KEY (CLASS NIL) (READ-TABLE NIL) (CONSTRUCTORS NIL)

    Reads JSON-STR and creates an according object.
    If CLASS is non-nil and represents a class, an instance of it is returned. Otherwise only built-in types can be deserialized.
    READ-TABLE makes it possible to inject specific readers, as pondons to SERIALIZE. It has the form of an alist containing the dispatch character as car and the deserialization function as cdr.
    CONSTRUCTORS holds an alist mapping the keyword returned by a specific reader to a object construction function.

  • [generic-function] SERIALIZE OBJ STREAM

    Serialize an object OBJ into STREAM. Implementations for built-in types already exist. The user might extend with methods for specific types.

  • [function] DESERIALIZE-RAW JSON-STR &KEY (READ-TABLE NIL)

    Deserialize JSON-STR into a property list. As opposed to DESERIALIZE-JSON this function does not require a base class to deserialize.

License Information

This library is released under the MIT License. Please refer to the LICENSE to get the full licensing text.

Contributing to this project

Please refer to the CONTRIBUTING document for more information.

Dependencies (5)

  • closer-mop
  • iterate
  • log4cl
  • parse-number
  • trivial-utilities

Dependents (1)

  • GitHub
  • Quicklisp