CL-RemiMarshal

CL-RemiMarshal is a library for Common Lisp that allows automatic marshalling/serializing of JSON, YAML, and RSConf data data to and from CLOS classes. It places emphasis on speed, and also ensuring the data is properly typed.

CL-RemiMarshal is entirely written and maintained by one person, Remilia Scarlet! If you want to support her and CL-RemiMarshal, you can buy her a coffee on Ko-Fi, or support her through Liberapay. Support is greatly appreciated for this volunteer effort ^_^

Buy Me a Coffee at ko-fi.com Donate using Liberapay

Releases

Releases can be found on the wiki.

How do I get set up?

Most dependencies can be installed via Quicklisp. Check the .asd file to see what you'll need. The CL-SDM and CL-RemiYaml dependencies cannot, however, be installed from Quicklisp at the time of writing. This can instead be obtained from:

Once you have CL-SDM and CL-RemiYaml, put it (and this library) somewhere where ASDF can find them, then run this in a REPL:

(asdf:load-system :cl-remimarshal)

Usage

CL-RemiMarshal is based around metaclasses, which allow you to use some additional slot options to define how to marshal/unmarshal (serialize/deserialize) data using CLOS classes. It also emphasizes type checking to better ensure data integrity and code correctness.

The package nicknames cl-rm:, cl-rm/json:, cl-rm/yaml:, cl-rm/rsconf:, and cl-rm/utils: are all defined for shorter typing.

(asdf:load-system :cl-remimarshal)

(defclass data-config ()
  ((store-data?
    :initarg :store-data?
    :initform nil
    :marshal-type :bool
    :accessor data-config-store-data-p))
  (:metaclass cl-remimarshal-yaml:yaml-unmarshallable))

(defclass config ()
  ((username
    :initarg :username
    :initform ""
    :marshal-type :string
    :accessor config-username)

   (hostname
    :initarg :hostname
    :initform nil
    :key-name "host"
    :marshal-type :string?
    :accessor config-hostname)
    
   (data-config
    :initform (make-instance 'data-config)
    :key-name "data"
    :marshal-type (:class data-config)))
  (:metaclass cl-remimarshal-yaml:yaml-unmarshallable))

;; Read a file into a new CONFIG instance.
(cl-remimarshal-yaml:unmarshal #P"/path/to/config.yaml" 'config)

;; Marshal a new instance to a string.
(cl-remimarshal-yaml:marshal-to-string (make-instance 'config))

The :marshal-type key can take any of the following values. The options with ? indicate "or null":

  • :string, :string?
  • :bool, :bool?
  • :list-bool, :list-bool?, :list?-bool, :list?-bool?
  • :list-string, :list-string?, :list?-string, :list?-string?
  • :uint8, :uint8?, :uint16, :uint16?, :uint24, :uint24?, :uint32 :uint32?, :uint64, :uint64?
  • :int8, :int8?, :int16, :int16?, :int24, :int24?, :int32 :int32?, :int64, :int64?
  • :single-float, :double-float
  • :list-uint8, :list-uint8?, :list-uint16, :list-uint16? :list-uint24, :list-uint24?, :list-uint32, :list-uint32? :list-uint64, :list-uint64?
  • :list-int8, :list-int8?, :list-int16, :list-int16?, :list-int24 :list-int24?, :list-int32, :list-int32?, :list-int64, :list-int64?
  • :list-single-float, :list-double-float
  • :list?-uint8, :list?-uint8?, :list?-uint16, :list?-uint16? :list?-uint24, :list?-uint24?, :list?-uint32, :list?-uint32? :list?-uint64, :list?-uint64?
  • :list?-int8, :list?-int8?, :list?-int16, :list?-int16? :list?-int24?, :list?-int24?, :list?-int32, :list?-int32? :list?-int64, :list?-int64?
  • :list?-single-float, :list?-double-float
  • :list?-single-float?, :list?-double-float?
  • :any (store the raw deserialized value, no type checking done).

It is also possible to marhsal/unmarshal data to/from nested CLOS classes. This is accomplished with the following values. See the files examples/unmarshal-to-class.lisp and examples/unmarshal-to-table.lisp for information on how to use these.

  • :class, :class-list, :class?, :class-list?
  • :class-table, :class-table?

Lastly, rather than using these pre-defined types, you can also write custom marshalling/unmarshalling functions. These use the :marshal-fn and :unmarshal-fn slot options, respectively. A large set of predefined ones for common usage patterns are in the :cl-remimarshal-utils package. See the files examples/custom-functions.lisp and examples/handling-nulls.lisp for how to use these.

Development

Style info

I use a slightly unorthodox style for my code. Aside from these differences, please use normal Lisp formatting.

  • Keep lines 118 characters or shorter. Obviously sometimes you can't, but please try. Use 115 characters for Markdown files, though.
  • I mark types using the form T/..... For example, T/SOME-NEAT-TYPE. For predicates on these, use SOME-NEAT-TYPE-P.
  • No tabs. Only spaces.

How do I contribute?

  1. Go to https://fossil.cyberia9.org/cl-remimarshal and clone the Fossil repository.
  2. Create a new branch for your feature.
  3. Push locally to the new branch.
  4. Create a bundle with Fossil that contains your changes.
  5. Get in contact with Remilia via email, Fediverse, or open a ticket.

Contributors

CL-RemiMarshal is under the GNU Affero General Public License version 3.