Bankster Contracts
This document describes practical contracts (what is guaranteed, what is “soft” vs “strict”, how the default registry is chosen, when exceptions are thrown, how the protocols behave) for Bankster’s core axis: Currency, Money, Registry records and the Monetary, Scalable and Accountable protocols.
This is not an API reference (it does not list all arities), but a guide to behavior.
1. Foundations (model and global rules)
- Bankster models money as data:
Money = (Currency, BigDecimal amount). Registryis the source of truth for currencies (resolving by ID/code/numeric-id, localization, country relations).- Computations are based on
BigDecimal(no silentdouble). - The “soft” API returns
nilwhen no match is found; the “strict” API throwsExceptionInfo. - Loading
io.randomseed.bankster.currencyauto-initializes the global registry fromconfig.ednby default. To disable this side effect (and initialize explicitly), bindio.randomseed.bankster/*initialize-registry*tofalsearoundrequire. - Namespace
io.randomseed.bankster.jsr-354is an experimental, work-in-progress Clojure semantic bridge inspired by JSR-354 (JavaMoney). It is not a Java implementation/interface of the standard. The goal is to progressively cover more of JSR-354 semantics in future Bankster releases; until then, treat this namespace as unstable. :weightis a registry attribute used to resolve conflicts when resolving by code and numeric ID (in:cur-code->cursand:cur-nr->curs). Weight is stored in the registry base map:cur-id->weight(exported as top-level:weightsin EDN). Currency instances stored in registries also carry the weight in metadata as an optimization. Weight is ignored byCurrency/Moneyequality and arithmetic.
1.1 Terminology
- Currency ID: a keyword, e.g.
:EURor:crypto/ETH(namespaced). - Currency code: an unqualified keyword, e.g.
:EUR,:ETH(from:crypto/ETHyou get:ETH). - Domain: a classification of a “currency world” (e.g.
:ISO-4217,:CRYPTO). For namespaced currencies it is derived from the namespace (upper-case). - Kind: a case-sensitive keyword classifying what a currency is. It may be namespaced (e.g.
:iso/fiat,:virtual/token). - Traits: advisory tags/features associated with a currency (stored in a registry), independent from currency identity and
Moneysemantics. - Scale:
- for a currency: nominal number of decimal places,
- for
Money: the scale of the amount (BigDecimalscale).
- Auto-scaled currency: a currency with an “automatic” scale (no fixed nominal scale; the amount carries scale).
- Weight: an integer where lower weight wins when resolving code / numeric-ID conflicts in a registry.
1.2 Error model
- Contract violations (wrong arity/typing combination, mismatched currencies, missing currency in registry, missing rounding-mode, etc.) are signaled via
clojure.lang.ExceptionInfo(ex-info). - In
ex-datayou will typically find keys like:registry,:currency,:value,:op, and/or domain-specific keys (:augend,:addend,:minuend,:subtrahend,:dividend,:divisor). - When an error originates from
BigDecimalarithmetic (JavaArithmeticException), Bankster rethrows it asExceptionInfoand marks it inex-datawith::arithmetic-exception true,:arithmetic-exception/cause(the originalArithmeticException, also used as the exception cause).
2. Records (core data)
2.1 io.randomseed.bankster/Currency
Fields:
:id(keyword) - unique currency identifier within a registry.:numeric(long) - numeric id (e.g. ISO 4217); absence is represented by a sentinel (no-numeric-id).:scale(int) - nominal currency scale; auto-scale is represented by a sentinel (auto-scaled).:kind(keyword or nil) - classification (case-sensitive; may be namespaced, e.g.:iso/fiat,:virtual/token).:domain(keyword or nil) - domain (e.g.:ISO-4217,:CRYPTO).
Non-inherent attribute:
:weight(int) - weight used to resolve conflicts when resolving by code and/or numeric ID (lower wins).- Source of truth: registry base map
:cur-id->weight(:weightsin EDN config). - Registry
Currencyinstances carry weight in metadata for hot paths (bucket sorting), accessible viacurrency/weight. - In EDN config
:weightmay be omitted (implicit 0). - Branch-oriented export (
importer/registry->map) emits weights under top-level:weights(presence is meaningful; explicit0is supported). - Currency-oriented export embeds per-currency
:weightinto:currencieswhile keeping orphaned:weightsentries.
- Source of truth: registry base map
Contracts:
Currencyis “data first”: field values are explicit; there are no hidden side effects.(.toString Currency)returns the code (i.e.(name :id)), not the full ID. Do not rely ontoStringin logs. For stable identification usecurrency/id(keyword) orcurrency/to-id-str(string without keyword interning).Currencyvalues may carry an extension map (extra keys) because they are Clojure records. Bankster itself keeps core semantics in record fields and treats extension keys as non-semantic metadata.- When loading currencies from EDN configuration, extra keys present in currency maps are ignored by default.
- To allow selected extra keys to be preserved on
Currencyobjects, use:propagate-keys(global allowlist) and/or per-currency:propagate-keys(override). Keys reserved by core currency construction and configuration pre-population (e.g.:id,:kind,:scale,:numeric,:domain,:weight,:countries,:localized,:traits, and the directive:propagate-keys) are never propagated.
2.2 io.randomseed.bankster/Money
Fields:
:currency- aCurrencyobject.:amount-java.math.BigDecimal.
Contracts:
Moneyalways carries aBigDecimal(inputs are coerced through thescalelayer).(.toString Money)returns"AMOUNT CODE"(code comes fromCurrency/toString).- The amount scale in
Moneyis part of the data (it may differ from the currency’s nominal scale). For auto-scaled currencies it reflects the current value’s scale (it adapts to the amount). This is supported, but has consequences (seemoney/rescaled?,money/rescale,money/strip).
2.3 io.randomseed.bankster/Registry
The registry is a record with indices (maps), including:
:cur-id->cur- currency ID ->Currency(canonical entry).:cur-code->curs- currency code -> a set of currencies (sorted, “weighted”).:cur-nr->cur- numeric-id ->Currency.:cur-nr->curs- numeric-id -> a set of currencies (sorted, “weighted”).:cur-dom->curs- domain -> a set of currencies (sorted, “weighted”).:ctr-id->cur- country ID ->Currency.:cur-id->ctr-ids- currency ID -> a set of country IDs.:cur-id->localized- currency ID -> localization/properties map.:cur-id->traits- currency ID -> traits (a set/vector of keywords; advisory).:cur-id->weight- currency ID -> int weight (conflict resolution).:hierarchies- aCurrencyHierarchiesrecord holding per-axis hierarchies (usually at least:domainand:kind, optionally:traitsand/or custom axes).:version- string (timestamp-style).:ext- extra data (map).
Contracts:
- The global registry lives in
io.randomseed.bankster.registry/R(Atom). - In many operations, “default registry” means: the dynamic
registry/*default*(when bound), otherwise the globalregistry/R. registry/new/registry/new-registryaccept base maps and build derived index maps (:cur-code->curs,:cur-nr->cur,:cur-nr->curs,:cur-dom->curs,:cur-id->ctr-ids) during initialization. Even the arity that accepts a Registry map/record ignores any derived index fields (they are recomputed during initialization).- Registry hierarchies are used by higher-level predicates that rely on
isa?(e.g.currency/of-domain?,currency/of-kind?,currency/of-trait?). Custom axes may be introduced by consumers (stored under additional keys in:hierarchies). - When multiple currencies share the same numeric ID, the registry keeps them all in
:cur-nr->cursand picks a canonical one for:cur-nr->curusing weight (lower wins).
3. Protocols
3.1 io.randomseed.bankster.currency/Monetary
Monetary is the “coercion + currency resolution” layer.
General rule:
to-*methods are cheap and registry-free (may returnnil).resolve/resolve-allconsult a registry and are “soft” (may returnnil).unit/of-idare “strict” (throw when the currency is missing from the registry), with an explicit conventionregistry=nilfor already constructedCurrencyvalues (see below).
Key methods and their contracts:
to-id-> keyword:- registry-free,
- may intern keywords (e.g. from a string) - do not use on untrusted input.
to-code-> keyword:- registry-free,
- returns an unqualified code.
to-id-str/to-code-str-> String:- registry-free,
- should not intern keywords (safer for untrusted input),
- canonicalization: upper-case name, namespace preserved (for ID).
to-numeric-id-> long/nil:- registry-free hint (for objects that carry a numeric id).
to-currency-> Currency/nil:- registry-free; may create an ad-hoc
Currency(e.g. from a map).
- registry-free; may create an ad-hoc
to-map-> map/nil:- registry-free; the map may be partial.
definitive?-> boolean:- whether the representation carries enough information to make meaningful negative property checks (e.g. “definitely not ISO”).
resolve-> Currency/nil:- soft: returns
nilwhen it cannot be resolved. - when
registryisnil, uses the default registry.
- soft: returns
resolve-all-> set/nil:- soft; returns a set of matches or
nil.
- soft; returns a set of matches or
id-> keyword:- unary:
- advisory / soft: returns what it can infer locally,
- may consult the default registry to disambiguate unqualified currency codes (e.g.
:BTC->:crypto/BTCwhen such currency exists), - does not throw on missing currencies (note: numeric IDs are different - see below).
- binary:
registry=nilmeans “do not consult a registry” (return local ID/coercion). - binary: when a registry is actually consulted, missing currency -> exception. For already constructed
Currencyvalues the registry is ignored and.idis returned. - binary:
registryshould be aRegistry(ornilas above). The boolean sentineltrueworks only syntactically via(registry/get true)(macro-level); passingtrueas a runtime value is not supported. - note: for numeric IDs (numbers),
idalways consults a registry and throws when the mapping is missing.
- unary:
of-id-> Currency:- strict: missing currency -> exception,
- when the argument is a
Currency:registry=nilmeans “return as-is”.
unit-> Currency:- strict: missing currency -> exception,
- when the argument is a
Currency:registry=nilmeans “return as-is”, - for maps: maps are treated as masks/constraints (match by key presence);
:idand:codehints are normalized (upper-case name, namespace preserved; ISO-4217 namespace stripped) before matching, and when multiple matches exist, the best match is selected by weight (lower wins). - for
Currencyobjects: field match is strict;:domain/:kindmay benilwildcards, but scale must match exactly (includingauto-scaled, which is not a wildcard).
defined?-> boolean:- existence of a currency in a registry (by ID/numeric/code); this is a “does anything exist” check, without validating full field-level consistency.
present?-> boolean:- checks whether the representation is consistent with the entry in the registry (field match).
Soft helpers:
currency/attemptandcurrency/attempt*:- soft coercion: returns a
Currencyornil, does not throw. - preferred in predicates and “try” paths.
- soft coercion: returns a
currency/unit-try:- soft variant of
unitwith identical semantics; returnsnilinstead of throwing.
- soft variant of
Constructor quick reference (Currency/Money)
| Function | Strictness | Description |
|---|---|---|
currency/new | hard | Create an ad-hoc Currency from ID/fields; may throw on invalid domain/namespace mismatch. |
currency/map->new | hard | Create an ad-hoc Currency from a map. |
currency/of (macro) | hard | Resolve via registry or create ad-hoc from a map; throws on missing when resolving. |
currency/unit | hard | Strict registry lookup (throws on missing). |
currency/unit-try | soft | Same as unit, but returns nil on failure. |
currency/resolve | soft | Registry lookup that returns nil when missing. |
currency/attempt | soft | Soft coercion; may return ad-hoc Currency without consulting registry. |
currency/of-id | hard | Strict lookup by currency ID. |
money/value | hard | Construct Money; may throw on missing currency or required rounding. |
money/of (macro) | hard | Macro constructor; throws on missing currency/rounding. |
money/of-major (macro) | hard | Like money/of, amount as the major part. |
money/of-minor (macro) | hard | Like money/of, amount as the minor part. |
money/major-value | hard | Function constructor for major part. |
money/minor-value | hard | Function constructor for minor part. |
money/of-map | hard | Construct Money from a map; throws on invalid input. |
3.2 io.randomseed.bankster.scale/Scalable
Scalable is the “what is scale” + “how to safely produce BigDecimal” layer.
Methods:
scalable?-> boolean: can this value be coerced to a scalable value?applied?-> boolean: does the value already carry scale information (e.g.BigDecimal,Money,Currency)?of-> long: scale (forMoneyit’s the amount scale; forCurrencyit’s the currency scale; for auto-scaled currencies this is-1).apply-> scaled value:- for numbers: returns a
BigDecimal, - for
Money: may rescale the amount; unaryapplyreapplies the currency’s nominal scale (when the currency is not auto-scaled). - for
Currency: returns aCurrencywith updated:scale.
- for numbers: returns a
amount-> BigDecimal:- for
Money: returns the amount, - for
Currency: returns0Mat nominal scale; returnsnilfor auto-scaled currencies, - for numbers: returns a
BigDecimal(after coercion).
- for
Helpers:
scale/auto?-> boolean/nil: checks whether(scale/of x)is auto-scaled; returnsnilwhen the value is not scalable (or not resolvable in a registry).
Front API namespaces:
API.mdcontains an overview of the front API surface.io.randomseed.bankster.api:amount->scale/amount.scale-> scale forMoneyandCurrency(Money amount scale / Currency nominal scale;-1for auto-scaled currencies).
io.randomseed.bankster.api.registry:- Registry helpers (
with,state,hierarchy-derive,hierarchy-derive!,or-default).or-defaulttreatsnilandtrueas “use default registry”.
- Registry helpers (
io.randomseed.bankster.api.money:- Money-only arithmetic:
add,sub,mul,div. - Money-only comparisons/predicates:
eq?,ne?,gt?,ge?,lt?,le?,compare,pos?,neg?,zero?.
- Money-only arithmetic:
io.randomseed.bankster.api.currency:- Currency helpers (unprefixed function set).
- Operator namespace for intentional
:refer :all:io.randomseed.bankster.api.ops. - Frozen API for major version 2:
io.randomseed.bankster.api.v2and sub-namespaces. It mirrorsio.randomseed.bankster.apifor the 2.x line and remains available when Bankster 3 appears. In the current release the v2 API is equivalent toio.randomseed.bankster.api.
Dynamic vars and rounding:
scale/*rounding-mode*:- default rounding mode used by scaling operations when rounding is needed.
scale/*each*:- when true, some operations (e.g. multi-arg division in money) rescale after each step.
scale/with-rounding:- binds
*rounding-mode*and sets a thread-local fast path for rounding-mode lookups (performance).
- binds
scale/with-rescaling:- binds
*each*+*rounding-mode*and sets a thread-local fast path for rounding-mode lookups (performance).
- binds
Recommendation:
- Prefer using
scale/with-rounding/scale/with-rescaling(or the aliases inio.randomseed.bankster.money) instead of a rawbindingonscale/*rounding-mode*in performance-sensitive code. Plainbindingremains supported, but it does not use the fast path and may be noticeably slower in tight numeric loops.
3.3 io.randomseed.bankster.money/Accountable
Accountable is the “what can become Money” + “how to convert Money across currencies/registries” layer.
Methods:
value-> Money/nil:- builds a
Moneyfrom numbers/strings/currency identifiers, etc., - when the amount is
nilit typically returnsnil, - when currency is missing and cannot be inferred, it throws,
- a rounding mode is required when coercion/scaling needs rounding and there is no explicit rounding mode and
scale/*rounding-mode*is not set.
- builds a
cast-> Money:- changes currency (with rescaling/rounding preserved),
- if you only need to ensure the currency comes from a given registry, prefer
money/of-registry.
4. Public function families (practical classification)
4.1 Registry API (io.randomseed.bankster.registry)
Creation and global state:
registry/new,registry/new-registry- create a new registry.registry/R- Atom holding the global registry.registry/get- returns a registry:(registry/get)uses the default registry (dynamic or global),(registry/get registry)prefers the provided registry unless it isnilorfalse,(registry/get true)works as a syntactic sentinel (macro-level): use the default registry.
registry/state-@R(global).registry/set!- sets the global registry.registry/update,registry/update!- functional / global update.registry/with- lexically binds the default registry (registry/*default*).registry/hierarchies,registry/hierarchy- access registry hierarchies.registry/hierarchy-derive,registry/hierarchy-derive!- update a selected hierarchy axis in a registry (pure / global mutation).
Read-only indices:
registry/currency-id->currency,registry/currency-code->currencies,registry/currency-nr->currency,registry/currency-domain->currencies, etc.registry/currency-id->traits- access per-currency traits.registry/version,registry/ext.
Diagnostics:
registry/*warn-on-inconsistency*,registry/*warnings-logger*,registry/inconsistency-warning.
4.2 Currency API (io.randomseed.bankster.currency)
Construction:
currency/new-currency,currency/new,currency/map->new:- canonicalize IDs (upper-case name; namespace preserved, except
ISO-4217which is stripped), - may infer
:domainas:ISO-4217under typical conditions, :weightdefaults to 0.
- canonicalize IDs (upper-case name; namespace preserved, except
Default currency / registry:
currency/*default*,currency/set-default!,currency/unset-default!.currency/set-default-registry!,currency/config->registry.
Resolution and coercion:
currency/of(macro) - convenient currency retrieval (from registry or ad-hoc for maps).currency/unit,currency/of-id- strict; throw when currency is missing from the registry.currency/resolve,currency/resolve-all- soft; returnnilwhen nothing matches.currency/attempt,currency/attempt*,currency/with-attempt- soft helpers.
Registry operations (mutate a registry value functionally; no side effects unless you use ! variants):
currency/register,currency/unregister,currency/update(+!variants on the global registry).currency/add-countries,currency/remove-countries(+!).currency/add-localized-properties,currency/remove-localized-properties(+!).currency/add-weighted-code(associate code with currency; conflicts resolved by weight).currency/set-weight,currency/clear-weight(+!) - narrow API to mutate the registry weight base (:cur-id->weight) while keeping weighted indices and canonical numeric resolution synchronized.currency/set-traits,currency/add-traits,currency/remove-traits(+!) - narrow API to mutate the registry traits base (:cur-id->traits).
Predicates and classification:
currency/currency?,currency/possible?,currency/definitive?,currency/defined?,currency/present?.currency/iso?,currency/iso-strict?,currency/iso-legacy?,currency/crypto?,currency/fiat?, etc.currency/of-domain?- hierarchy-aware domain predicate (usesregistry/:hierarchieswhen present).currency/of-kind?- hierarchy-aware kind predicate (usesregistry/:hierarchieswhen present).currency/has-trait?(exact membership) andcurrency/of-trait?(hierarchy-aware) - trait predicates backed byregistry/:cur-id->traitsand optionallyregistry/:hierarchies/:traits.currency/null?,currency/none?- nil/empty/null-currency helpers.currency/same-ids?- identity comparison by ID (soft, symmetric).
Properties and localization:
currency/id,currency/code,currency/ns-code,currency/nr,currency/sc,currency/domain,currency/kind,currency/weight.currency/info- full currency info map (fields + registry metadata).currency/countries,currency/localized-properties,currency/localized-property.currency/domains,currency/of-domain- domain selectors.currency/symbol,currency/display-name(+*-native).currency/formatter,currency/formatter-extended.
Tagged literals:
#currency ...(viacurrency/code-literal/currency/data-literal).
4.3 Money API (io.randomseed.bankster.money)
Creation / parsing:
money/value(Accountable) - the primary constructor function.money/of,money/of-major,money/of-minor(macro) - ergonomic creation.money/major-value,money/minor-value- creation via major/minor parts.money/parse,money/parse-major,money/parse-minor- internal parsers (public, but mostly low-level).money/of-registry- forces the currency in Money to come from the given registry (and aligns scale).
Properties and inspection:
money/amount,money/currency,money/stripped-amount,money/unparse.money/money?,money/rescaled?,money/auto-scaled?.
Comparisons and predicates:
money/compare,money/compare-amounts(strict: require matching currency;nilis comparable and is the “lowest”).money/eq?,money/eq-am?(==),money/ne?,money/ne-am?(not==).money/gt?,money/ge?,money/lt?,money/le?.money/is-pos?,money/is-neg?,money/is-zero?,money/is-pos-or-zero?,money/is-neg-or-zero?(+ aliasespos?,neg?,zero?).
Arithmetic (general rule: currencies must be “the same currency”, but weight is ignored):
money/add(+),money/sub(-):- accept
Moneyand require matching currency, - do not allow adding/subtracting plain numbers to/from
Money.
- accept
money/mul(*):- allows
Money* numbers, - supports at most one
Moneyargument in the whole expression; otherwise throwsExceptionInfo.
- allows
money/div(/):Money / number-> Money,Money / Money(same currency) ->BigDecimal,number / Money-> exception.- unary
money/divworks likeclojure.core//for numbers, but throws forMoney(because it would be equivalent tonumber / Money).
money/rem:- analogous to
div: forMoney/Moneythe result isBigDecimal, forMoney/numberthe result isMoney.
- analogous to
Scaling / rounding:
money/scale,money/rescale,money/round,money/round-to,money/strip(use consciously).- Context macros:
money/with-rounding,money/with-rescaling(aliases toscale/...).
Allocation:
money/allocate:- splits the amount into parts according to integer-like ratios,
- the sum of parts equals the original exactly (sum-preserving),
- the remainder is distributed deterministically left-to-right.
money/distribute:allocatewith ratios(repeat n 1).
Formatting:
money/format,money/format-with(use formatters fromcurrency).
Tagged literals / readers:
#money ...(viamoney/code-literal/money/data-literal+*-cryptovariants).money/data-readers,money/code-readers.
4.4 Inter-op layers (operators)
io.randomseed.bankster.money.ops:- operator aliases (
+,-,*,/,=, etc.) that always mean Money semantics.
- operator aliases (
io.randomseed.bankster.money.inter-ops:- if there is no
Moneyargument, behaves 1:1 likeclojure.core, - if there is any
Moneyargument, it “taints” the operation and switches to Bankster semantics. - this layer is intentionally polymorphic; boxed math warnings are suppressed in that namespace to keep build output clean.
- if there is no
5. Recommendations and pitfalls (practical)
- Do not identify currencies by
toString. Usecurrency/idorcurrency/to-id-str. - Avoid
clojure.core/=forMoneycomparisons (it uses record/map equality, comparesBigDecimalvalues using Clojure numeric equality (scale-insensitive), and may reflect non-semantic differences like currency extension keys). Prefermoney/eq?(ormoney/=) /money/eq-am?(money/==), or the inter-op layerio.randomseed.bankster.money.inter-ops/=for mixed numeric expressions. - Avoid
:refer :allonio.randomseed.bankster.api(it adds noise). Useio.randomseed.bankster.api.opswhen you intentionally want money-aware operators. - For untrusted input (e.g. from an API) prefer
to-id-str/to-code-strand validate, instead of callingkeyword(interning). - Be explicit about rounding: set
scale/*rounding-mode*viascale/with-roundingor pass rounding-mode explicitly. - Beware Clojure floating-point literals (precision). For money, prefer:
- BigDecimal literals with
M, or - strings (parsed into BigDecimal).
- BigDecimal literals with
- Auto-scaled currencies carry scale in the amount. This can be convenient, but requires care when interoperating with fixed-scale systems.
6. Serialization (io.randomseed.bankster.serializers.*)
Bankster provides per-format serialization protocols for JSON and EDN. For detailed usage and examples see doc/50_serialization.md.
6.1 Protocols
JSON (io.randomseed.bankster.serializers.json):
JsonSerializable:to-json-map,to-json-full-map,to-json-stringJsonDeserializable:from-json-map,from-json-string
EDN (io.randomseed.bankster.serializers.edn):
EdnSerializable:to-edn-map,to-edn-full-map,to-edn-stringEdnDeserializable:from-edn-map,from-edn-string
Implementations: Money, Currency, Class (type token for deserialization).
6.2 Serialization contracts
to-*-map-> map/nil:- returns minimal representation by default (currency ID + amount only),
- with
:full? truedelegates toto-*-full-map, - returns
nilwhen input isnil.
to-*-full-map-> map/nil:- returns full representation with all fields,
- for
Money: currency is serialized as a nested map (not string ID), - for
Money: includes extended fields (from record extension map), - accepts
:keysoption for filtering; supports nested opts via map elements, - returns
nilwhen input isnil.
to-*-string-> String/nil:- returns canonical string representation,
- JSON:
"<amount> <currency-id>"(e.g."12.30 PLN"), - EDN: tagged literal (e.g.
#money[12.30M PLN],#money/crypto[1.5M ETH]), - returns
nilwhen input isnil.
6.3 Deserialization contracts
from-*-map-> Money/Currency:- strict: throws
ExceptionInfowhen:- input is not a map,
- required key is missing (
:currency,:amountfor Money;:idfor Currency), - currency cannot be resolved in registry,
:rounding-modecannot be parsed (when provided). Accepts:RoundingMode, keywords (:HALF_UP), or strings ("HALF_UP").
- returns
nilwhen input isnil.
- strict: throws
from-*-string-> Money/Currency:- strict: throws
ExceptionInfowhen:- input is not a string,
- string cannot be parsed,
- currency cannot be resolved in registry.
- returns
nilwhen input isnil.
- strict: throws
6.4 Registry, rounding, and rescaling behavior
- Deserialization uses the default registry (
registry/get) unless:registryis provided in opts. - Amount is rescaled to currency’s nominal scale during deserialization, unless
:rescaleoption overrides it. - When rescaling requires rounding and no
:rounding-modeis provided (neither in opts nor inscale/*rounding-mode*), anArithmeticExceptionis thrown (wrapped asExceptionInfo). :rounding-modeaccepts:java.math.RoundingModeobjects, keywords (:HALF_UP), or strings ("HALF_UP","ROUND_HALF_UP"). Parsed viascale/post-parse-rounding.- Invalid
:rounding-modevalues (provided but not parseable) throwExceptionInfoin both serialization and deserialization paths.
6.4.1 The :rescale option
Serialization with :rescale:
- Rescales the amount to the specified scale before outputting.
- Downscaling requires
:rounding-modeorscale/*rounding-mode*. - Does not modify the Money object itself, only the serialized output.
Deserialization with :rescale:
- Overrides the currency’s nominal scale from the registry.
- The Currency object is cloned with the
:rescalevalue as its scale. - The resulting Money carries this modified Currency (not the registry’s version).
- Use case: preserving precision when incoming data has more decimal places than the registry currency’s scale.
Contract:
:rescalemust be a non-negative integer when provided.- Invalid
:rescale(non-integer or negative) throwsExceptionInfoon both serialization and deserialization. - When
:rescaleisnil, standard behavior applies (use currency’s nominal scale). :rescaleapplies to both JSON and EDN serializers identically.
6.5 Nil handling
All serialization and deserialization functions return nil when given nil input. They do not throw on nil.
6.6 Map key acceptance (JSON)
JSON deserialization accepts both keyword and string keys in input maps:
:currencyor"currency":amountor"amount":idor"id"
EDN deserialization expects keyword keys only.
JSON numeric precision caveat: parsing JSON numbers into double loses precision before Bankster sees the data. For production, prefer amount as a string, or configure your JSON parser to emit BigDecimal (e.g. Cheshire via cheshire.parse/*use-bigdecimals?*).
6.7 Extended fields
Moneyrecords may carry extended fields viaassoc.to-*-full-mapincludes them in output.- JSON: extended field values are stringified (BigDecimal →
.toPlainString, keyword →name, other →str). - EDN: extended field values are preserved as-is.
- Extended fields are not restored during deserialization (they are not part of the core
Moneyschema).
6.8 Cheshire integration
register-cheshire-codecs!registers a Cheshire encoder forMoney.- Throws
ExceptionInfowhen Cheshire is not on the classpath. - Does not register a decoder; decoding must be done explicitly via
json-map->money/json-string->money.