Kazoo JSON
Manipulating JSON objects in Erlang.
Accessors
Getters
The kz_json
module provides a generic accessor, get_value(Path, JObj[, Default])
that will return the value at Path
('undefined'
or Default
if missing).
There are also a number of accessors that will make sure the value retrieved is of a certain type; otherwise the Default
is used. Functions like get_ne_binary_value
will make sure the Value
is a non-empty binary, for instance.
Another accessor is get_first_defined(Paths, JObj[, Default])
which takes a list of paths to search, returning the first non-'undefined'
value found iterating through Paths
; otherwise 'undefined'
or Default
is returned.
get_keys(JObj)
: returns the top-level keys of theJObj
.get_values(JObj)
: will return a tuple{Values, Keys}
from the object (similar tolists:unzip/2
).values(JObj)
: returns just the top-levelValues
of the object.new()
: returns an empty JSON object.
Setters
set_value(Path, Value, JObj)
will putValue
atPath
(including creating nested objects as necessary).set_values([{Path, Value}], JObj)
will putValues
atPaths
(including creating nested objects as necessary).insert_value(Path, Value, JObj)
will only putValue
atPath
if there isn’t a value there already.insert_values([{Path, Value}], JObj)
will only putValue
atPath
if there isn’t a value there already.
Deleting keys
delete_key(Path, JObj[, 'prune' | 'no_prune'])
: will delete the value at Path from the JObj. You can optionally prune the resulting empty object ('no_prune'
is the default).delete_keys(Paths, JObj)
: will deletePaths
using'no_prune'
. Useprune_keys(Paths, JObj)
if you want empty objects pruned.
Note that set_value(Path, 'null', JObj)
is equivalent to delete_key(Path, JObj)
.
Predicates
Several predicates exist that will return booleans.
is_true(Path, JObj[, Default])
: returns if the value atPath
is “true” in some form (atom, list, binary).is_false
exists as well for “false”.is_defined(Path, JObj)
: Returns if the value atPath
is not'undefined'
.is_empty(JObj)
: returnstrue
ifJObj
is the empty JSON object ({[]}
currently but could be#{}
if the switch to maps is made).is_json_object(JObj)
,are_json_objects(JObjs)
: Superficial check for the internal JSON structure. Useis_valid_json_object(JObj)
to recurse into the data structure.is_json_term(Term)
: returns if theTerm
is aJSON-able
term. Terms likepid()
,reference()
, and such have no parallel in JSON and are thus considered to not be JSON terms.are_equal(JObj1, JObj2)
: compares two objects for equality, irrespective of how the underlying ordering of keys.
Converters
Away from JSON objects
to_proplist(JObj)
: Returns the top-level object as a proplist instead (nested objects remain objects).to_map(JObj)
: Returns a map representation of the objectrecursive_to_proplist(JObj)
: Converts nested objects to proplists as well
To JSON objects
from_list(Proplist)
: Top-level change from proplist to objectfrom_map(Map)
: Converts map back to JSON object data structurefrom_list_recursive(Proplist)
: Equivalent tofrom_list_recursive/2
with default option:#{ascii_list_enforced => 'true', invalid_as_null => 'true'}
from_list_recursive(Proplist, #{}=Options)
: Converts nested proplists to objects as well. Options are:ascii_list_enforced
: If the value of a proplist item is list and all of the elements are ASCII characters, convert the value to binary. This also converts empty list[]
to binary. If you are sure that you don’t have any string value, but expect and empty list as value, set this option tofalse
to not convert the empty list to binaryinvalid_as_null
: If the value of a proplist item is not any oflist()
,binary()
,atom()
,integer()
,float()
,kz_time:date()
,kz_time:datetime()
,kz_json:object()
orkz_term:proplist()
then set the value tonull
, otherwise throw{'error', kz_term:ne_binary()}
Merging Objects
You can merge two (or more) objects together and specify which objects’ value will be used when there are conflicting keys.
merge([JObj1, JObj2,...])
: Defaults to using themerge_right
strategy where the object on the “right” will have its value used when both objects have a value at a given key path.merge(Strategy, JObjs)
: Choose a strategy, eitherfun kz_json:merge_right/2
orfun kz_json:merge_left/2
(or define your own), and apply it to the list of objects.
Both merge_left/2
and merge_right/2
will keep the left or right empty objects (respectively) when they exist at a given key path if the other value is also an object (empty or not). To override this behavior and always recursively merge the values when both are objects, specify 'recursive' => 'true'
in the Options
map passed in merge(JObjs, Options)
, merge(JObj1, JObj2, Options)
, merge(Strategy, JObjs, Options)
, or merge(JObj1, JObj2, Options)
, as arg 4 to merge/4
, or as arg 5 to merge/5
.
Handling null
Sometimes it is beneficial to include the null
atom as a value (for downstream processing perhaps) versus triggering a delete_key
equivalent.
set_value
and merge
can take an optional map #{'keep_null' => 'true'}
to ensure null
is kept in the resulting data structure.
List-like operations
JSON objects are iterable and kz_json
has equivalents to the lists
module for filter/{2,3}
, filtermap/2
, map/2
, fold{l,r}/3
, foreach/2
, all/2
, and any/2
among others.
Another of note is find(Path, JObjs[, Default])
which searchs a list of JSON objects for a key path. There is also a find_first_defind(Paths, JObjs[, Default])
which will find(Path, JObjs)
, continuing to the next Path
if 'undefined'
is returned.
There is also a find_value(Path, Value, JObjs[, Default])
which will find the first JObj
in the list that has Value
at Path
.
Lifting common JSON properties
Basic usage
kz_json:lift_common_properties([JObj1, JObj2]) ->
{CommonJObj, [UniqueJObj1, UniqueJObj2]}.
All key/value pairs common to each JObj in the list will be collected into CommonJObj
and removed from each JObj
.
Blacklists
Sometimes you know there are keys that shouldn’t be lifted (like leg-only variables on endpoints vs channel variables):
kz_json:lift_common_properties([JObj1, JObj2,...], [Key1, Path2]) ->
{CommonJObj, [UniqueJObj1, UniqueJObj2,...]}.
This will not allow Key1
and Path2
to be in CommonJObj
and will remain in the objects that have it set. Note that the blacklist can be keys (<<"key">>
) or paths ([<<"key">>, <<"subkey">>]
).