This is an experimental zipper library for the json4s AST.
The goal of this library is to implement purely functional modifications to immutable JSON structures.
This library depends on json4s-core, not on any of the json4s parsing libraries. To follow these examples, you'll need
a project that depends on json4s-native
or json4s-jackson
. (Or, you'll need to construct the JSON example by hand
using the AST.)
The tests for json4s-zipper use the native parser, so if you have SBT installed, and a copy of the source, you can run
sbt test:console
to try these examples.
To start with, here is some JSON, parsed into the JValue
AST.
import org.json4s.native.JsonMethods._
val json = parse("""{"soups":["goulash","gumbo","minestrone"]}""")
This library has support for modifying elements of a JSON structure using an xpath-like syntax.
import com.gu.json.syntax._
import org.json4s.JsonAST._
// Append the string " is tasty!" to each string in the array within the field "soups"
val tastySoups = json.mod ("soups" \ *) { case JString(s) => JString(s + " is tasty!") }
println(compact(render(tastySoups)))
// {"soups":["goulash is tasty!","gumbo is tasty!","minestrone is tasty!"]}
It's a little verbose, but you can use the JCursor
API directly. Most operations result in an Option[JCursor]
, as
they may fail (e.g. if you use field
when the focus is on a JArray
).
val cursor = json.cursor // Create a cursor focusing on the root of the `JValue`
val updatedCursor = for {
a <- cursor.field("soups") // Go to field "soups"
b <- a.prepend(JString("borscht")) // Prepend to the array
} yield b
for (c <- updatedCursor) println(compact(render(c.toJson)))
// {"soups":["borscht","goulash","gumbo","minestrone"]}
Lenses enable bidirectional transformations on data structures; i.e. the ability to query and update a view of the structure, with modifications propagating back as changes to the original structure.
This library implements Scalaz partial lenses for JValue
structures. The get and putback operations are implemented
using the cursor API, but lenses provide a more composable API, with a whole host of lens-related operations for free.
The partiality of the lenses is a result of the potential absence of expected elements in the JValue
structure. Get
and set operations return an Option
, and modify operations which fail will return the original structure unmodified.
import com.gu.json.Lenses._
// A partial lens focusing on the string value of the 2nd element of field "soups"
val pLens = field("soups") >=> elem(1) >=> strVal
// The lens can be used simply to view the value at that location
pLens get json
// Some(gumbo)
// The lens can also be used to transform the value
val updatedJson = pLens mod ("shellfish " + _, json)
println(compact(render(updatedJson)))
// {"soups":["goulash","shellfish gumbo","minestrone"]}
See LensExamples for more examples.