metosin / jsonista

Clojure library for fast JSON encoding and decoding.
https://cljdoc.org/d/metosin/jsonista
Eclipse Public License 2.0
422 stars 30 forks source link

Support custom keyword handling #12

Closed ikitommi closed 7 years ago

ikitommi commented 7 years ago

remove :keywordize? in favour of:

  | `:encode-key-fn`    | true to coerce keyword keys to strings, false to leave them as keywords, or a function to provide custom coercion (default: true) |
  | `:decode-key-fn`    | true to coerce keys to keywords, false to leave them as strings, or a function to provide custom coercion (default: false) |"

here's the relevant tests:

(def +kw-mapper+ (jsonista/object-mapper {:decode-key-fn true}))
(def +upper-mapper+ (jsonista/object-mapper {:decode-key-fn str/upper-case}))
(def +string-mapper+ (jsonista/object-mapper {:decode-key-fn false}))
(deftest options-tests
  (let [data {:hello "world"}]
    (testing ":decode-key-fn"
      (is (= {"hello" "world"} (-> data jsonista/write-value-as-string jsonista/read-value)))
      (is (= {:hello "world"} (-> data (jsonista/write-value-as-string) (jsonista/read-value +kw-mapper+))))
      (is (= {"hello" "world"} (-> data (jsonista/write-value-as-string) (jsonista/read-value +string-mapper+))))
      (is (= {"HELLO" "world"} (-> data (jsonista/write-value-as-string) (jsonista/read-value +upper-mapper+)))))
    (testing ":encode-key-fn"
      (let [data {:hello "world"}]
        (is (= "{\"hello\":\"world\"}" (jsonista/write-value-as-string data (jsonista/object-mapper {:encode-key-fn true}))))
        (is (= "{\":hello\":\"world\"}" (jsonista/write-value-as-string data (jsonista/object-mapper {:encode-key-fn false}))))
        (is (= "{\"HELLO\":\"world\"}" (jsonista/write-value-as-string data (jsonista/object-mapper {:encode-key-fn (comp str/upper-case name)}))))))
    (testing ":pretty"
      (is (= "{\n  \"hello\" : \"world\"\n}" (jsonista/write-value-as-string data (jsonista/object-mapper {:pretty true})))))
    (testing ":escape-non-ascii"
      (is (= "{\"imperial-money\":\"\\u00A3\"}" (jsonista/write-value-as-string {:imperial-money "£"} (jsonista/object-mapper {:escape-non-ascii true})))))
    (testing ":date-format"
      (is (= "{\"mmddyyyy\":\"00-01-70\"}" (jsonista/write-value-as-string {:mmddyyyy (Date. 0)} (jsonista/object-mapper {:date-format "mm-dd-yy"})))))))

With defaults, use the same code as before, so no change in perf. Except one can transform keys in single sweep making things much faster.

ikitommi commented 7 years ago

next time then. Dunno about the virgil, I'll submit an issue there.

chrisdavies commented 7 years ago

So, this could be used to convert to/from camelCase by passing opts like so:

{:encode-key-fn (comp name to-camel-case)
 :decode-key-fn (comp to-kebab-case keyword)}

Assuming the to-camel-case and to-kebab-case have a signature something like this:

; Takes a keyword :foo-bar and converts it to a string "fooBar"
(defn- camel-case [k]
  (clojure.string/replace k #"[-_](.)" #(-> % (nth 1) clojure.string/upper-case))))

If so, then this pull request means I can switch from Cheshire to jsonista. Nice work!

ikitommi commented 7 years ago

Yes:

(require '[jsonista.core :as j])

(defn reverse-string [s] (apply str (reverse s)))

(def mapper 
  (j/object-mapper {:encode-key-fn (comp reverse-string name)
                    :decode-key-fn (comp keyword reverse-string)}))

(-> {:kikka "kukka"} 
    (doto prn) 
    (j/write-value-as-string mapper) 
    (doto prn) 
    (j/read-value mapper) 
    prn)
; {:kikka "kukka"}
; "{\"akkik\":\"kukka\"}"
; {:kikka "kukka"}