This small project is an attempt to reduce the complexity of maintaining multiple versions of JSON documents.
It includes following:
- A CSS-based query language for querying JSON nodes.
- A mutable AST for transforming documents based on queries.
- A Groovy-based DSL for writing transformations.
A list of transformations supported:
- Add a JSON value to a node.
- Rename a JSON key.
- Delete a node from the tree.
- Move a JSON key/value pair up or sideways in the tree
- Merge a JSON node with another node.
- Partition a JSON node into more nodes.
Given a JSON document:
{
"destination": {
"value": 42
},
"subtree": {
"source": "Jon Snow dies in Season 5",
"foo": "bar",
"baz": "botch",
"stop": "the shitty examples"
}
}
You can select all nodes with the name stop
by writing:
def document = JsonDocument.parse(...) // some input file
document.select("stop")
The result will be an instance of JsonNodes
, that has a reference to all the matching nodes.
You can write some more complex selectors:
document.select("subtree source")
This will select all nodes with the name source
that have a ancestor with the name subtree
.
Following selectors are supported:
name
gets all nodes matching the name..class
gets all the nodes that have a child@class
with a value (or array containing) the classname.#id
gets all the nodes that have a child@id
with the valueid
.
Combinations of selectors are supported:
ancestorSelector selector
gets all nodes matchingselector
that also have a ancestor matchingancestorSelector
.parentSelector > selector
gets all nodes matchingselector
that have a parent matchingparentSelector
.[keyName]
gets all nodes that have a child namedkeyName
.selector[keyName^=Prefix]
gets all nodes matchingselector
that also have a child namedkeyName
whose value starts withPrefix
.selector[keyName*=Substring]
gets all nodes matchingselector
that also have a child namedkeyName
whose value containsSubstring
.selector[keyName=SomeName]
gets all nodes matchingselector
that also have a child namedkeyName
whose value equalsSomeName
.
Besides the selector language, the framework allows transformation of JSON trees.
An example:
document.transform("source")
.moveTo("destination")
.apply()
document.transform("stop")
.renameTo("we love")
.apply()
document.transform("subree")
.deleteChild("foo")
.deleteChild("baz")
.apply()
The resulting JSON will be:
{
"destination": {
"source": "Jon Snow dies in Season 5",
"value": 42
},
"subtree": {
"we love": "the shitty examples"
}
}
The partition function splits a node into one or more sibling nodes.
document.transform("subtree")
.partition([["original_source", "source"], ["random_stuff", "foo", "baz", "stop"]])
.apply()
The resulting JSON:
{
"destination": {
"value": 42
},
"original_source": {
"source": "Jon Snow dies in Season 5"
},
"random_stuff": {
"foo": "bar",
"baz": "botch",
"stop": "the shitty examples"
},
"subtree": {}
}
You will need a JsonDocument
instance.
This can be created either by parsing the file with the included BaseNodeParser
or by using the JacksonConverter
class to convert a Jackson-based JSON tree.
The JsonDocument
class has a method for helping out with this:
def document = JsonDocument.parse(some_input_stream)
document.transform(...) // Ready for transformation
It's possible to define transformation as .groovy
files.
The syntax is as follows:
version 1
comment "Renames the 'name' property to 'billy' and adds a dog named bingo."
transformations {
transform("name")
.renameTo("billy")
.apply()
transform("person")
.addJson("dog", '{"type":"dog", "name": "bingo"}')
.apply()
}
The script defines a version, a comment and a closure containing the transformations.
The closure is executed with a JsonDocument
as delegate.
To run these transformations you have to use an instance of the VersionControl
class.
Following code shows the steps needed to do so:
def control = new VersionControl(new File("/path/to/directory/with/transformation scripts"))
control.apply(document, desiredVersionNumber)
Every script in the directory is read.
The version number used in the apply
method will be the last transformation that is applied.