Bankster provides per-format serialization protocols for JSON and EDN, with a clean separation between minimal and full representations.
| Namespace | Purpose |
|---|---|
io.randomseed.bankster.serializers.json | JSON serialization (strings for amounts, string IDs) |
io.randomseed.bankster.serializers.edn | EDN serialization (BigDecimals, keyword IDs, tagged literals) |
(defprotocol JsonSerializable
(to-json-map [this] [this opts]) ; minimal or full map
(to-json-full-map [this] [this opts]) ; always full map
(to-json-string [this] [this opts])) ; canonical string
(defprotocol JsonDeserializable
(from-json-map [type-token m] [type-token m opts])
(from-json-string [type-token s] [type-token s opts]))
(defprotocol EdnSerializable
(to-edn-map [this] [this opts]) ; minimal or full map
(to-edn-full-map [this] [this opts]) ; always full map
(to-edn-string [this] [this opts])) ; tagged literal
(defprotocol EdnDeserializable
(from-edn-map [type-token m] [type-token m opts])
(from-edn-string [type-token s] [type-token s opts]))
By default, serialization produces minimal output:
| Type | JSON minimal | EDN minimal |
|---|---|---|
Currency | {:id "PLN"} | {:id :PLN} |
Money | {:currency "PLN", :amount "12.30"} | {:currency :PLN, :amount 12.30M} |
Full serialization (to-*-full-map or :full? true) includes all fields:
| Type | JSON full | EDN full |
|---|---|---|
Currency | {:id, :numeric, :scale, :kind, :domain} | {:id, :numeric, :scale, :kind, :domain} |
Money | {:currency {...}, :amount} + extended fields | {:currency {...}, :amount} + extended fields |
| Key | Type | Context | Description |
|---|---|---|---|
:code-only? | boolean | ser | Omit namespace: :crypto/ETH → "ETH" (JSON) / :ETH (EDN) |
:full? | boolean | ser | Delegate to-*-map to to-*-full-map |
:keys | vector | ser | Filter output keys; supports nested opts for recursive serialization |
:rescale | integer | ser/deser | Scale to apply to amounts (see Rescaling section) |
:registry | Registry | deser | Registry for deserialization (default: registry/get) |
:rounding-mode | RoundingMode/keyword/string | ser/deser | Rounding mode for rescaling |
The :rounding-mode option accepts multiple formats:
java.math.RoundingMode object: RoundingMode/HALF_UP:HALF_UP, :HALF_DOWN, :CEILING, etc."HALF_UP", "ROUND_HALF_UP"When not specified, falls back to scale/*rounding-mode* dynamic var.
:keys filteringThe :keys option supports nested options via map elements:
;; Filter Money to only :amount and currency's :id/:numeric
(to-json-full-map money {:keys [:amount {:currency {:keys [:id :numeric]}}]})
;; => {:amount "12.30", :currency {:id "PLN", :numeric 985}}
Syntax for :keys vector:
{:field-name {:keys [...]}} select that field with nested optionsMoney records may carry extended fields (via assoc). Full serialization includes
them:
(def m (assoc (money/of :PLN 100) :memo "Payment"))
(to-json-full-map m)
;; => {:currency {...}, :amount "100.00", :memo "Payment"}
Extended fields in JSON are stringified (BigDecimal → .toPlainString, keyword → name,
other → str). In EDN they are preserved as-is.
| Aspect | JSON | EDN |
|---|---|---|
| Currency ID | String ("PLN", "crypto/ETH") | Keyword (:PLN, :crypto/ETH) |
| Amount | String (.toPlainString) | BigDecimal |
| Tagged literal | N/A | #money[12.30M PLN], #money/crypto[1.5M ETH] |
| Kind | String with namespace ("iso/fiat") | Keyword with namespace (:iso/fiat) |
| Domain | String ("ISO-4217") | Keyword (:ISO-4217) |
Both namespaces expose helper functions that mirror protocol methods:
;; JSON
(money->json-map m) ; minimal
(money->json-full-map m) ; full
(money->json-string m) ; "12.30 PLN"
(json-map->money m)
(json-string->money s)
(currency->json-map c) ; minimal {:id "PLN"}
(currency->json-full-map c) ; full
(currency->json-string c) ; "PLN"
(json-map->currency m)
(json-string->currency s)
;; EDN
(money->edn-map m) ; minimal
(money->edn-full-map m) ; full
(money->edn-string m) ; "#money[12.30M PLN]"
(edn-map->money m)
(edn-string->money s)
(currency->edn-map c) ; minimal {:id :PLN}
(currency->edn-full-map c) ; full
(currency->edn-string c) ; "#currency :PLN"
(edn-map->currency m)
(edn-string->currency s)
(edn-keyword->currency k)
(require '[io.randomseed.bankster.serializers.json :as sj])
;; Register Money encoder with Cheshire (map representation)
(sj/register-cheshire-codecs!)
;; Or use string representation
(sj/register-cheshire-codecs! {:representation :string})
;; With :code-only? option
(sj/register-cheshire-codecs! {:code-only? true})
Note: Cheshire registration affects encoding only. Decoding remains explicit via
json-map->money / json-string->money.
Both namespaces provide money-codec for building encode/decode function pairs:
(let [{:keys [encode decode]} (sj/money-codec {:representation :map
:code-only? true
:registry my-reg
:rounding-mode RoundingMode/HALF_UP})]
(encode money) ; -> map or string
(decode data)) ; -> Money
The codec returns a map with:
:encode - function (Money -> json-value):decode - function (json-value -> Money):representation - :map or :string(require '[io.randomseed.bankster.serializers.json :as sj]
'[io.randomseed.bankster.serializers.edn :as se])
;; JSON minimal (default)
(sj/money->json-map #money[12.30 PLN])
;; => {:currency "PLN", :amount "12.30"}
;; JSON full
(sj/money->json-full-map #money[12.30 PLN])
;; => {:currency {:id "PLN", :numeric 985, :scale 2, :kind "iso/fiat", :domain "ISO-4217"},
;; :amount "12.30"}
;; EDN minimal
(se/money->edn-map #money[12.30 PLN])
;; => {:currency :PLN, :amount 12.30M}
;; EDN tagged literal
(se/money->edn-string #money[12.30 PLN])
;; => "#money[12.30M PLN]"
(se/money->edn-string #money/crypto[1.5 ETH])
;; => "#money/crypto[1.500000000000000000M ETH]"
(sj/json-map->money {:currency "PLN" :amount "12.30"})
;; => #money[12.30 PLN]
(sj/json-string->money "12.30 PLN")
;; => #money[12.30 PLN]
(se/edn-string->money "#money[12.30M PLN]")
;; => #money[12.30 PLN]
;; With custom registry and rounding
(sj/json-map->money {:currency "PLN" :amount "1.005"}
{:rounding-mode java.math.RoundingMode/HALF_UP})
;; => #money[1.01 PLN]
;; Via to-json-map with :full? true
(sj/to-json-map #money[12.30 PLN] {:full? true})
;; => {:currency {:id "PLN", ...}, :amount "12.30"}
;; Via protocol
(sj/to-json-map (currency/of :PLN) {:full? true})
;; => {:id "PLN", :numeric 985, :scale 2, :kind "iso/fiat", :domain "ISO-4217"}
;; Select specific fields
(sj/money->json-full-map #money[12.30 PLN] {:keys [:amount {:currency {:keys [:id :numeric]}}]})
;; => {:amount "12.30", :currency {:id "PLN", :numeric 985}}
;; Currency only
(sj/currency->json-full-map (currency/of :PLN) {:keys [:id :scale]})
;; => {:id "PLN", :scale 2}
;; Omit namespace in currency IDs
(sj/money->json-map #money/crypto[1.5 ETH] {:code-only? true})
;; => {:currency "ETH", :amount "1.500000000000000000"}
(se/money->edn-string #money/crypto[1.5 ETH] {:code-only? true})
;; => "#money[1.500000000000000000M ETH]"
The :rescale option allows serializing/deserializing Money with a different scale
than the currency's nominal scale.
:rescaleRescale amounts before outputting:
;; Upscale PLN (scale 2) to 4 decimal places
(sj/money->json-map #money[12.30 PLN] {:rescale 4})
;; => {:currency "PLN", :amount "12.3000"}
;; Downscale requires rounding mode (or throws ArithmeticException)
(sj/money->json-map #money/crypto[1.12345 ETH] {:rescale 2 :rounding-mode :HALF_UP})
;; => {:currency "crypto/ETH", :amount "1.12"}
;; Works with all serialization functions
(sj/money->json-string #money[12.30 PLN] {:rescale 4})
;; => "12.3000 PLN"
:rescalePreserve precision when the incoming data has more decimal places than the registry
currency. Without :rescale, the amount would be truncated to the currency's nominal
scale, potentially losing data.
;; PLN has scale 2, but incoming data has 4 decimal places
;; Without :rescale - data loss!
(sj/json-map->money {:currency "PLN" :amount "12.3456"})
;; => ArithmeticException (or truncated if rounding-mode set)
;; With :rescale - precision preserved
(sj/json-map->money {:currency "PLN" :amount "12.3456"} {:rescale 4})
;; => #money[12.3456 PLN] (Currency has scale 4, not the registry's 2)
;; The resulting Money has a Currency with the custom scale
(let [m (sj/json-map->money {:currency "PLN" :amount "12.3456"} {:rescale 4})]
(.scale (currency/of m)))
;; => 4
:rescale works in deserialization:rescale value as its scaleThis means the resulting Money object carries a Currency with a non-standard scale. This is intentional - it preserves all information from the wire format without data loss.
:rescaleCan 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 |