Serializing sorted maps in Clojure / EDN?

1.9k views Asked by At

How can I serialize and deserialize a sorted map in Clojure?

For example:

(sorted-map :a 1 :b 2 :c 3 :d 4 :e 5)
{:a 1, :b 2, :c 3, :d 4, :e 5}

What I've noticed:

  1. A sorted map is displayed in the same way as an unsorted map in the REPL. This seems convenient at times but inconvenient at others.
  2. EDN does not have support for sorted maps.
  3. Clojure does support custom tagged literals for the reader.

Additional resources:

2

There are 2 answers

1
Michał Marczyk On BEST ANSWER

Same question with two usable answers: Saving+reading sorted maps to a file in Clojure.

A third answer would be to set up custom reader literals. You'd print sorted maps as something like

;; non-namespaced tags are meant to be reserved
#my.ns/sorted-map {:foo 1 :bar 2}

and then use an appropriate data function when reading (converting from a hash map to a sorted map). There's a choice to be made as to whether you wish to deal with custom comparators (which is a problem impossible to solve in general, but one can of course choose to deal with special cases).

clojure.edn/read accepts an optional opts map which may contain a :reader key; the value at that key is then taken to be a map specifying which data readers to use for which tags. See (doc clojure.edn/read) for details.

As for printing, you could install a custom method for print-method or use a custom function for printing your sorted maps. I'd probably go with the latter solution -- implementing built-in protocols / multimethods for built-in types is not a great idea in general, so even when it seems reasonable in a particular case it requires extra care etc.; simpler to use one's own function.

Update:

Demonstrating how to reuse IPersistentMap's print-method impl cleanly, as promised in a comment on David's answer:

(def ^:private ipm-print-method
  (get (methods print-method) clojure.lang.IPersistentMap))

(defmethod print-method clojure.lang.PersistentTreeMap
  [o ^java.io.Writer w]
  (.write w "#sorted/map ")
  (ipm-print-method o w))

With this in place:

user=> (sorted-map :foo 1 :bar 2)
#sorted/map {:bar 2, :foo 1}
2
David J. On

In data_readers.clj:

{sorted/map my-app.core/create-sorted-map}

Note: I wished that this would work, but it did not (not sure why):

{sorted/map clojure.lang.PersistentTreeMap/create}

Now, in my-app.core:

(defn create-sorted-map
  [x]
  (clojure.lang.PersistentTreeMap/create x))

(defmethod print-method clojure.lang.PersistentTreeMap
  [o ^java.io.Writer w]
  (.write w "#sorted/map ")
  (print-method (into {} o) w))

As an alternative -- less low-level, you can use:

(defn create-sorted-map [x] (into (sorted-map) x))

The tests:

(deftest reader-literal-test
  (testing "#sorted/map"
    (is (= (sorted-map :v 4 :w 5 :x 6 :y 7 :z 8)
           #sorted/map {:v 4 :w 5 :x 6 :y 7 :z 8}))))

(deftest str-test
  (testing "str"
    (is (= "#sorted/map {:v 4, :w 5, :x 6, :y 7, :z 8}"
           (str (sorted-map :v 4 :w 5 :x 6 :y 7 :z 8))))))

Much of this was adapted from the resources I found above.

Note: I am surprised that print-method works, above. It would seem to me that (into {} o) would lose the ordering and thus bungle up the printing, but it works in my testing. I don't know why.