Search code examples
clojurecoercionplumatic-schema

Using Plumatic Schema to coerce to bigdec


I have incoming data of the type {:loan/amount 1200}.

Would it be possible to use plumatic Schema to coerce this to {:loan/amount 1200M}, ie coercing numbers (or even strings of digits) to bigdecimals?

I don't how to define a new data type (like s/Bigdec) and then make sure it uses clojure.core/bigdec to coerce a certain value to a java.math.BigDecimal.


Solution

  • There are two separate concepts in Schema: validation and coercion.

    For the first one you need to define your schema. Classes are treated as schemas so you don't need to create a custom one for java.math.BigDecimal. Your schema might look like:

    (require '[schema.core :as s])
    (require '[schema.coerce :as c])
    
    (s/defschema Loan {:loan/amount java.math.BigDecimal})
    

    Now you can validate your data against the schema:

    (s/validate Loan {:loan/amount 10M})
    ;; => {:loan/amount 10M}
    

    Now if you have some data that you would like to coerce you need to define a coercion function which is a matcher from a desired target schema (java.math.BigDecimal in your case) to a function that will convert the actual value to the desired bigdec value.

    (def safe-bigdec (c/safe bigdec)
    

    schema.coerce/safe is a utility function which wraps the original function and if the original one throws an exception when called, safe will return original input value instead of throwing the exception.

    Our matcher function will check if the current schema element is BigDecimal and return converting function or nil otherwise (meaning there is no coercion for other types):

    (defn big-decimal-matcher [schema]
      (when (= java.math.BigDecimal schema)
        safe-bigdec))
    

    And finally we need a coercer for performing actual coercion:

    (def loan-coercer (c/coercer Loan big-decimal-matcher))
    

    With all the setup we can now use our coercer:

    (loan-coercer {:loan/amount "12.34"})
    ;; => {:loan/amount 12.34M}
    
    (loan-coercer {:loan/amount 1234})
    ;; => {:loan/amount 1234M}
    
    (loan-coercer {:loan/amount "abc"})
    ;; => #schema.utils.ErrorContainer{:error {:loan/amount (not (instance? java.math.BigDecimal "abc"))}}