Extended and extensible types for JSON. An implementation of Argus.
JSON is a popular, but anemic data format. It supports four scalar types and two container types. This is a far cry from the richness of the real world of programming languages, and yet ... it is a robust and universal foundation if only it supported tagged values.
I get it. I'm not trying to xkcd a data format. My goal here is to produce and consume JSON. This JSON can be run through any JSON parser in any language, it can be stored and queried in a PostgreSQL database, it can easily be read with human eyeballs. Yet, there's a big wooden horse hiding arbitrary data types in plain sight...tagged values.
A tagged value is a map with a single key/value pair where the key is a tag. For example:
{"#set" : [1]}
For more information see the Argus "manifesto."
Tagged values are a good idea! This is not an entirely new idea. MongoDB has an Extended JSON format. DynamoDB has Data Type Descriptors. Neither of these are user extensible and available as external an library.
The jsonista library has an object mapper for reading and writing arbitrary tagged JSON data, but it is a JVM-only library.
In the Clojure/Script world there are two other ways to use tagged values (EDN and Transit), so one could reasonably ask why another library/format/etc.?
In particular argus overlaps with Transit. Like Transit, argus embraces JSON for its universality. And like Transit, argus tags data and is extensible.
argus technically isn't even a JSON library. It just rewrites Clojure data to Clojure data that is restricted to valid JSON values.
It works with both Clojure and ClojureScript, so it is suitable for sending rich data to and from backend and frontend.
Because argus transparently handles keywords, symbols, and strings as map keys, I do not recommend that you have your JSON library automatically convert map keys into keywords. This will interfere with the map key decoding process, and there are some cases where it is useful to maintain string keys in data (for example, when dealing—not with "objects"—but "mappings" from one name to another).
I wouldn't say that performance is not a concern, but my top priorities are:
That said, I want argus to be performant, and I have tried to make it performant when I can. If you have performance improvements, I'm happy to take them.
user> (require '[systems.thoughtfull.argus :as argus])
nil
user> (argus/enargus (argus/argus) #{1 2 3})
{"#set" [1 3 2]}
user> (argus/deargus (argus/argus) {"#set" [1 3 2]})
#{1 3 2}
user> (defrecord CustomType [a b])
user.CustomType
user> (def a (argus/argus :encoders {CustomType ["#my/type" (juxt :a :b)]}
:decoders {"#my/type" (partial apply ->CustomType)}))
#'user/a
user> (argus/enargus a (->CustomType 1 2))
{"#my/type" [1 2]}
user> (argus/deargus a {"#my/type" [1 2]})
#user.CustomType{:a 1, :b 2}
Run Clojure tests with:
clojure -X:test
Run ClojureScript tests with:
npm run test && node target/test.js
Copyright © technosophist
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
Ctrl+k | Jump to recent docs |
← | Move to previous article |
→ | Move to next article |
Ctrl+/ | Jump to the search field |