A scala implementation of the RFC-6901 and RFC-6902. It also provides methods to compute diffs between two Json values that produce valid Json patches.
- Getting Started
- Json Library
- Basic Usage
- Remembering old values
- Patches as Collections of Operations
- Technical Details
This library is published in the Maven Central Repository. You can add it to your sbt project by putting this line into your build description:
libraryDependencies += "org.gnieh" %% f"diffson-$jsonLib" % "2.2.4"
where jsonLib
is either:
spray-json
play-json
circe
If you are using maven, add the following dependency to your pom.xml
:
<dependency>
<groupId>org.gnieh</groupId>
<artifactId>diffson-${json.lib}_${scala.version}</artifactId>
<version>2.2.4</version>
</dependency>
These versions are built for Scala 2.11, 2.12, and 2.10 when the underlying json library still works with 2.10.
A snapshot version is also published for the next major release 3. To use it add the dependency with release numer 3.0.0-SNAPSHOT
to your build.
Diffson was first developped for spray-json, however, it is possible to use it with any json library of your linking.
The only requirement is to have a DiffsonInstance
for your json library.
At the moment, diffson provides two instances for spray-json, Play! Json, and circe. To use these implementations you need to link with the correct module and import the instance:
// spray-json
import gnieh.diffson.sprayJson._
// play-json
import gnieh.diffson.playJson._
// circe
import gnieh.diffson.circe._
If you want to add support for your favorite Json library, you may only depend on diffson core module diffson-core
and all you need to do then is to implement the gnieh.diffson.DiffsonInstance
class, which provides the JsonProvider
for the specific library. Contribution of new Json libraries in this repository are more than welcome.
Although the library is quite small and easy to use, here comes a summary of its basic usage.
There are three different entities living in the gnieh.diffson
package:
JsonPointer
which allows to parse and manipulate Json pointers as defined in RFC-6901,JsonPatch
which allows to parse, create and apply Json patches as defined in RFC-6902,JsonDiff
which allows to compute the diff between two Json values and create Json patches.
Basically if one wants to compute the diff between two Json objects, on can execute the following:
val json1 = """{
| "a": 1,
| "b": true,
| "c": "test"
|}""".stripMargin
val json2 = """{
| "a": 6,
| "c": "test2",
| "d": false
|}""".stripMargin
val patch = JsonDiff.diff(json1, json2, false)
println(patch)
which will print the following in the console:
[{
"op":"replace",
"path":"/a",
"value":6
},{
"op":"remove",
"path":"/b"
},{
"op":"replace",
"path":"/c",
"value":"test2"
},{
"op":"add",
"path":"/d",
"value":false
}]
You can then apply the patch to json1
:
val json3 = patch(json1)
println(json3)
which prints something like:
{
"d":false,
"c":"test2",
"a":6
}
which we can easily verify is the same as json2
modulo reordering of fields.
You may also only want to apply existing patches:
val raw = """[
| {
| "op": "test",
| "path": "/a",
| "value": 4
| }
|]""".stripMargin
val patch = JsonPatch.parse(raw)
val json1 = """{ "a": 4 }"""
val json2 = """{ "a": 7 }"""
patch(json1) // ok json1 is returned unchanched
patch(json2) // throws PatchException
The diff
method takes three parameter. The third one indicates whether the generated patch remembers removed and replaced values.
When set to true
, the Replace
and Remove
operations take an extra field named old
giving the old value.
The RFC does not define these fields, but it does not fordbid either to add extra fields. Hence, generated patches can still be interpreted by third party implementations.
Patches may be seen as collections of operations on which you may which to apply some typical collection functions such as map
, flatMap
, filtering, folding, ...
val patch: JsonPatch = ...
val patch2: JsonPatch =
for(op @ Add("my" :: "prefix" :: _, _) <- patch)
yield op
The diff between two arrays is computed by using the Patience Diff algorithm to compute the LCS between both arrays, which is quite simple to implement.
However one can replace the implementation by any other algorithm that implements the gnieh.diffson.Lcs
trait, e.g.:
val diff = new JsonDiff(new MyLcsAlgorithm)
then use diff
in lieu of JsonDiff
in the first usage example.