HierarchicalUtils.jl
JsonGrinder.jl
uses HierarchicalUtils.jl
which brings a lot of additional features.
using HierarchicalUtils
Let's say we have a complex Schema
, which we want to further inspect:
julia> using JSON
julia> jss = JSON.parse("""[ { "a": { "b": "foo", "c": [5, 6] }, "d": "bar" }, { "d": "baz" }, { "a": { "c": [] }, "b": "foo" } ]""");
julia> sch = schema(jss)
DictEntry 3x updated ├── a: DictEntry 2x updated │ ├── b: LeafEntry (1 unique `String` values) 1x updated │ ╰── c: ArrayEntry 2x updated │ ╰── LeafEntry (2 unique `Real` values) 2x updated ├── b: LeafEntry (1 unique `String` values) 1x updated ╰── d: LeafEntry (2 unique `String` values) 2x updated
In small enough schema, all types of nodes are visible, but it gets more complicated if the schema does not fit your screen. Let's see how we can use HierarchicalUtils.jl
to programmatically examine sch
.
First, the whole tree (regardless of the display area size) can be printed with
julia> printtree(sch)
DictEntry 3x updated ├── a: DictEntry 2x updated │ ├── b: LeafEntry (1 unique `String` values) 1x updated │ ╰── c: ArrayEntry 2x updated │ ╰── LeafEntry (2 unique `Real` values) 2x updated ├── b: LeafEntry (1 unique `String` values) 1x updated ╰── d: LeafEntry (2 unique `String` values) 2x updated
Callling with trav=true
enables convenient traversal functionality with string indexing:
julia> printtree(sch, trav=true)
DictEntry [""] 3x updated ├── a: DictEntry ["E"] 2x updated │ ├── b: LeafEntry (1 unique `String` values) ["I"] 1x updated │ ╰── c: ArrayEntry ["M"] 2x updated │ ╰── LeafEntry (2 unique `Real` values) ["O"] 2x updated ├── b: LeafEntry (1 unique `String` values) ["U"] 1x updated ╰── d: LeafEntry (2 unique `String` values) ["k"] 2x updated
This way any element in the schema is swiftly accessible, which may come in handy when inspecting model parameters or simply deleting/replacing/inserting nodes in the tree. All tree nodes are accessible by indexing with the traversal code:
julia> sch["O"]
LeafEntry storing 2 unique `Real` values: 5 => 1 6 => 1
The following two approaches give the same result:
julia> sch["O"] ≡ sch[:a][:c].items
true
We can iterate over specific nodes in the schema. Let's for example collect all its leaves:
julia> LeafIterator(sch) |> collect
4-element Vector{LeafEntry}: LeafEntry (1 unique `String` values) LeafEntry (2 unique `Real` values) LeafEntry (1 unique `String` values) LeafEntry (2 unique `String` values)
or all DictEntry
nodes:
julia> TypeIterator(DictEntry, sch) |> collect
2-element Vector{DictEntry}: DictEntry DictEntry
We can for example get all traversal codes for nodes matching a given predicate:
julia> codes = pred_traversal(sch, n -> n.updated ≥ 2)
5-element Vector{String}: "" "E" "M" "O" "k"
We can even get Accessors.jl
optics:
julia> optic = code2lens(sch, "M") |> only
(@o _.children[:a].children[:c])
which can be used to access the nodes too (as well as many other operations):
using Accessors
julia> getall(sch, optic) |> only
ArrayEntry 2x updated ╰── LeafEntry (2 unique `Real` values) 2x updated
For the complete showcase of possibilities, refer to the HierarchicalUtils.jl
manual.