Flattening a nested record to its leaves Note, I discovered a bug after I posted this. The current implementation drops any fields that have an associative value, which is not the intended behavior. 



I'm writing a Tamagotchi clone, and need to be able to save the game's state to the disk. I want to save the entire state since it contains data other than the pet data like settings. Since de/serializing nested records is a pain, I decided to "flatten the nested records to their leaves". By that I mean if I have a record representing the game settings:

(defrecord Sim-Settings [health-per-tick pain-per-tick hunger-per-tick])
(defrecord Question-Settings [pain-per-wrong food-per-right])

(defrecord Settings [sim-settings question-settings])

(def test-settings
  (-&gt;Settings
    (-&gt;Sim-Settings 1 1 1)
    (-&gt;Question-Settings 1 1)))


I want to flatten that to:

(flatten-to-leaves test-settings)

{:health-per-tick 1,
 :pain-per-tick 1,
 :hunger-per-tick 1,
 :pain-per-wrong 1,
 :food-per-right 1}


All of the intermediate keys (:sim-settings and question-settings) are removed.

After struggling for a couple hours, I finally managed to get something working, but it's not perfect. All the solutions to similar problems I could find used tree-seq, which I had never used before. It's a cool function, but I'm afraid I might be forcing its usage here. 

(defn flatten-to-leaves [record]
  (into {}

    (filter
      ; ... and filter out the non-leaves.
      #(and (map-entry? %)
            (not (associative? (second %))))

      ; Turn the record into a list of nodes of the map...
      (tree-seq associative? (partial apply list) record))))


Parts of it are nice. I like how tree-seq basically just turned this into a filtering problem.

My main problem is with the filtering function though. It is, I hate to admit, a product of fiddling. It works in every test case I've thrown at it, but something about it seems off. I have a feeling it can be reduced to something simpler.

I appreciate any critique regarding this code.

If I flatten the entire state:

(defrecord Pet [satiation max-satiation health max-health])
(defrecord Question-Generator [operator-ranges])
(defrecord Sim-Settings [health-per-tick pain-per-tick hunger-per-tick])
(defrecord Question-Settings [pain-per-wrong food-per-right])
(defrecord Settings [sim-settings question-settings])

(defrecord Game-State [pet q-gen last-update settings])


I get something like:

{:max-satiation 100,
 :hunger-per-tick 0.00347,
 :pain-per-tick 0.00347,
 :satiation 100,
 :pain-per-wrong 2,
 :max-health 100,
 :health-per-tick 0.0075,
 :last-update "20170905T204200.285Z",
 :food-per-right 5,
 :health 100}
I think this is good usage of tree-seq. It's intended for walking trees, right? If your settings will have ragged depth it's probably the cleanest approach.

The only main cleanup I see is that this predicate can be pointfree if you want:

#(and (map-entry? %)
      (not (associative? (second %))))
; =
(every-pred map-entry? (comp not associative? second))


On the other hand, I don't see a huge need for records here at all, as opposed to maps.


Nested maps will de/serialize fine as-is with EDN (or JSON if there's nothing crazy in them)
They're idiomatic across the Clojure community
Define a clojure.spec spec for them if you want
Records don't give you type safety anyway, and you're not using any of their class-abilities like extending protocols.
Records are a bit faster if you're building thousands, but that doesn't look like the case here