This is a project in Clojure that displays the data structure for data made of arbitrary nesting of maps, sets, vectors, and sequences. It probably works in ClojureScript, but I've not tried. The multivating use case of the project is to understand andmanipulate the complicated structures returned from a GraphQL query. I've not made this tool for industrial use, so deal with bugs at your own risk. I welcome bug reports or pull requests.
Since this tool is only meant to be used in the REPL, I have not tried to package it up. Just put it in your source path and require the namespace.
user=> (require '[check-type :refer [check-default check pcheck]])
Using it on a non-collection type is the same as (type)
.
user=> (check 5)
java.lang.Long
Using it on a flat collection basically calls (check)
on the contents and puts it in the same collection type.
user=> (check [5 3 7 8 6 10])
[java.lang.Long]
It combines maps while keeping keys separate (configurable).
user=> (check [{:a 4, :b 3} {:a 8 "b" 9}])
[{:a java.lang.Long, :b java.lang.Long, "b" java.lang.Long}]
Arbitrarily deep data structures are merged recursively.
user=> (check [{:a [{:a 3 :b ["a" "b" "c"]}] :b "b"}
{:a [{ :b ["d"]}]}])
[{:a [{:a java.lang.Long, :b [java.lang.String]}], :b java.lang.String}]
Mixed type collections are handled like the following. Since map values don't have a nice way to be displayed in a sequential collection, they are indicated with [:union (types...)]
.
user=> (check [4 "string"])
[java.lang.Long java.lang.String]
user=> (check [{:a 4} {:a "string"}])
[{:a [:union (java.lang.Long java.lang.String)]}]
To pass configuration parameters, use a map as the second parameter as shown below. Alternatively, the defaults are stored in a atomic map called check-default
as defined below, which you can modify in your setup.
(def check-default
(atom {:eval-lazy :peek
:combine-keys [number?]
:combine-numbers true
:preserve-vals [keyword?]
:levels 5}))
To prevent cluttered output (and stack overflow), I've set a max recursion limit. It can be modified using the :levels
parameter. Set it to negative for unlimited recursion.
user=> (check [[[[[[:a]]]]]])
[[[[[clojure.lang.PersistentVector]]]]]
user=> (check [[[[[[:a]]]]]] {:levels 10})
[[[[[[:a]]]]]]
Clojure has multiple types for numerical values. You can combine them into a single type like the following.
user=> (check [3 3.5 4/3])
[java.lang.Long java.lang.Double clojure.lang.Ratio]
user=> (check [3 3.5 4/3] {:combine-numbers true})
[:number]
There are cases where you might want to combine different keys in a map, like when the key is a customer ID. :combine-keys
accepts a list of predicate functions, and the to a map is combined if any of the functions return true.
user=> (check [{3 "3" 4 "4"} {"strkey" "strval"}]
{:combine-keys []})
[{3 java.lang.String, 4 java.lang.String, "strkey" java.lang.String}]
user=> (check [{3 "3" 4 "4"} {"strkey" "strval"}]
{:combine-keys [number?]})
[{java.lang.Long java.lang.String, "strkey" java.lang.String}]
user=> (check [{3 "3" 4 "4"} {"strkey" "strval"}]
{:combine-keys [(constantly true)]})
[{java.lang.Long java.lang.String, java.lang.String java.lang.String}]
There are certain values you might not want to convert to types, like keywords. The :preserve-vals
accepts a list of predicate functions like :combine-keys
.
user=> (check {:key :val} {:preserve-vals []})
{:key clojure.lang.Keyword}
user=> (check {:key :val} {:preserve-vals [keyword?]})
{:key :val}
Since lazy sequences can be infinite or computationally expensive to compute, you can choose to not evaluate a lazy sequence, only evaluate the first member, or evaluate the entire sequence.
user=> (check (map inc [1 2.5 3/4])
{:combine-numbers false :eval-lazy :none})
clojure.lang.LazySeq
user=> (check (map inc [1 2.5 3/4])
{:combine-numbers false :eval-lazy :peek})
(java.lang.Long)
user=> (check (map inc [1 2.5 3/4])
{:combine-numbers false :eval-lazy :all})
(clojure.lang.Ratio java.lang.Double java.lang.Long)
For convenience, I created a helper function call pcheck
which has the same signature as check
and passes the output to pprint
.