├── JsonDiffPatchTests
├── Samples
│ ├── scene3a.json
│ ├── scene3b.json
│ ├── scene1b.json
│ ├── scene1a.json
│ ├── scene2a.json
│ ├── LoadTest1.json
│ └── scene2b.json
├── packages.config
├── app.config
├── TestTests.cs
├── DiffTests2.cs
├── RemoveTests.cs
├── PatchTests2.cs
├── ReplaceTests.cs
├── MoveTests.cs
├── JsonDiffPatch.Tests.csproj
├── CopyTests.cs
├── PatchTests.cs
├── AddTests.cs
└── DiffTests.cs
├── JsonDiffPatch
├── packages.config
├── RemoveOperation.cs
├── JsonDiffPatch.csproj
├── AddOperation.cs
├── ReplaceOperation.cs
├── TestOperation.cs
├── CopyOperation.cs
├── MoveOperation.cs
├── Operation.cs
├── AbstractPatcher.cs
├── Tavis.JsonPointer
│ └── JsonPointer.cs
├── ArrayLcs.cs
├── PatchDocument.cs
├── JsonPatcher.cs
└── JsonDiffer.cs
├── .gitattributes
├── JsonDiffPatch.sln
├── readme.md
├── .gitignore
└── License.txt
/JsonDiffPatchTests/Samples/scene3a.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": [
3 | "cash: 400",
4 | "product: 100"
5 | ]
6 | }
--------------------------------------------------------------------------------
/JsonDiffPatchTests/Samples/scene3b.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": [
3 | "cash: 700",
4 | "product: 000"
5 | ]
6 | }
--------------------------------------------------------------------------------
/JsonDiffPatchTests/Samples/scene1b.json:
--------------------------------------------------------------------------------
1 | {
2 | "items": [
3 | {
4 | "id": "viewpoint",
5 | "entities": [
6 | {
7 | "name": "Donn Crimmins (sdf)"
8 | }
9 | ],
10 | "type": "Viewpoint"
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/JsonDiffPatch/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/Samples/scene1a.json:
--------------------------------------------------------------------------------
1 | {
2 | "items": [
3 | {
4 | "id": "viewpoint",
5 | "entities": [
6 | {
7 | "name": "Donn Crimmins (sdf)"
8 | },
9 | {
10 | "name": "Chester Westberg"
11 | }
12 | ],
13 | "type": "Viewpoint"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/JsonDiffPatchTests/Samples/scene2a.json:
--------------------------------------------------------------------------------
1 | {
2 | "incidents": [
3 | {
4 | "when": "2015-09-08T12:15:25+01:00",
5 | "id": "7f468da7-3788-40c0-90fd-b06bc61c86dd",
6 | "where": "Fey St",
7 | "what": [
8 | "Franklyn Sanfilippo (aaaa) says \"1\""
9 | ],
10 | "type": "Incident"
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/JsonDiffPatchTests/Samples/LoadTest1.json:
--------------------------------------------------------------------------------
1 | [
2 | { "op": "test", "path": "/a/b/c", "value": "foo" },
3 | { "op": "remove", "path": "/a/b/c" },
4 | { "op": "add", "path": "/a/b/c", "value": ["foo", "bar"] },
5 | { "op": "replace", "path": "/a/b/c", "value": 42 },
6 | { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
7 | { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
8 | ]
--------------------------------------------------------------------------------
/JsonDiffPatchTests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/Samples/scene2b.json:
--------------------------------------------------------------------------------
1 | {
2 | "incidents": [
3 | {
4 | "when": "2015-09-08T12:15:46+01:00",
5 | "id": "dab4a570-2dde-458e-92b1-f71e42605f90",
6 | "where": "Fey St",
7 | "what": [
8 | "Franklyn Sanfilippo (aaaa) says \"2\""
9 | ],
10 | "type": "Incident"
11 | },
12 | {
13 | "when": "2015-09-08T12:15:25+01:00",
14 | "id": "7f468da7-3788-40c0-90fd-b06bc61c86dd",
15 | "where": "Fey St",
16 | "what": [
17 | "Franklyn Sanfilippo (aaaa) says \"1\""
18 | ],
19 | "type": "Incident"
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/JsonDiffPatchTests/TestTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using JsonDiffPatch;
3 | using Newtonsoft.Json.Linq;
4 | using NUnit.Framework;
5 |
6 | namespace Tavis.JsonPatch.Tests
7 | {
8 | public class TestTests
9 | {
10 | [Test]
11 | public void Test_a_value()
12 | {
13 |
14 | var sample = PatchTests.GetSample2();
15 |
16 | var patchDocument = new PatchDocument();
17 | var pointer = new JsonPointer("/books/0/author");
18 |
19 | patchDocument.AddOperation(new TestOperation(pointer, new JValue("Billy Burton")));
20 |
21 | Assert.Throws(typeof(InvalidOperationException), () =>
22 | {
23 | var patcher = new JsonPatcher();
24 | patcher.Patch(ref sample, patchDocument);
25 | });
26 |
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/JsonDiffPatch/RemoveOperation.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using Tavis;
6 |
7 | namespace JsonDiffPatch
8 | {
9 | public class RemoveOperation : Operation
10 | {
11 | public RemoveOperation()
12 | {
13 |
14 | }
15 |
16 | public RemoveOperation(JsonPointer path) : base(path)
17 | {
18 | }
19 |
20 | public override void Write(JsonWriter writer)
21 | {
22 | writer.WriteStartObject();
23 |
24 | WriteOp(writer, "remove");
25 | WritePath(writer, Path);
26 |
27 | writer.WriteEndObject();
28 | }
29 |
30 | public override void Read(JObject jOperation)
31 | {
32 | Path = new JsonPointer(SplitPath((string)jOperation.GetValue("path")));
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/JsonDiffPatch/JsonDiffPatch.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard1.4;net452;netcoreapp1.1
5 | Library for diffing and RFC 6902 patching json.net json objects - forked from Tavis.JsonPatch, with an addition of json diff code by Ian Mercer, with additional partial array LCS diff by JC Dickinson
6 | https://github.com/mcintyre321/JsonDiffPatch/blob/master/License.txt
7 |
8 | https://github.com/mcintyre321/JsonDiffPatch
9 | JSON, Diff, Patch, RFC, 6902
10 | True
11 | 1.0.0
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/JsonDiffPatch/AddOperation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using Tavis;
4 |
5 | namespace JsonDiffPatch
6 | {
7 | public class AddOperation : Operation
8 | {
9 | public JToken Value { get; private set; }
10 |
11 | public AddOperation()
12 | {
13 |
14 | }
15 |
16 | public AddOperation(JsonPointer path, JToken value) : base(path)
17 | {
18 | Value = value;
19 | }
20 |
21 | public override void Write(JsonWriter writer)
22 | {
23 | writer.WriteStartObject();
24 |
25 | WriteOp(writer, "add");
26 | WritePath(writer,Path);
27 | WriteValue(writer,Value);
28 |
29 | writer.WriteEndObject();
30 | }
31 |
32 | public override void Read(JObject jOperation)
33 | {
34 |
35 | Path = new JsonPointer(SplitPath((string)jOperation.GetValue("path")));
36 | Value = jOperation.GetValue("value");
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/JsonDiffPatch/ReplaceOperation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using Tavis;
4 |
5 | namespace JsonDiffPatch
6 | {
7 | public class ReplaceOperation : Operation
8 | {
9 | public JToken Value { get; private set; }
10 |
11 | public ReplaceOperation()
12 | {
13 |
14 | }
15 |
16 | public ReplaceOperation(JsonPointer path, JToken value) : base(path)
17 | {
18 | Value = value;
19 | }
20 |
21 | public override void Write(JsonWriter writer)
22 | {
23 | writer.WriteStartObject();
24 |
25 | WriteOp(writer, "replace");
26 | WritePath(writer, Path);
27 | WriteValue(writer, Value);
28 |
29 | writer.WriteEndObject();
30 | }
31 |
32 | public override void Read(JObject jOperation)
33 | {
34 | Path = new JsonPointer(SplitPath((string)jOperation.GetValue("path")));
35 | Value = jOperation.GetValue("value");
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/JsonDiffPatch/TestOperation.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using Tavis;
6 |
7 | namespace JsonDiffPatch
8 | {
9 | public class TestOperation : Operation
10 | {
11 | public JToken Value { get; private set; }
12 |
13 | public TestOperation()
14 | {
15 | }
16 |
17 | public TestOperation(JsonPointer path, JToken value) : base(path)
18 | {
19 | Value = value;
20 | }
21 |
22 | public override void Write(JsonWriter writer)
23 | {
24 | writer.WriteStartObject();
25 |
26 | WriteOp(writer, "test");
27 | WritePath(writer, Path);
28 | WriteValue(writer, Value);
29 |
30 | writer.WriteEndObject();
31 | }
32 |
33 | public override void Read(JObject jOperation)
34 | {
35 | Path = new JsonPointer(SplitPath((string)jOperation.GetValue("path")));
36 | Value = jOperation.GetValue("value");
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/JsonDiffPatch/CopyOperation.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using Tavis;
4 |
5 | namespace JsonDiffPatch
6 | {
7 | public class CopyOperation : Operation
8 | {
9 | public JsonPointer FromPath { get; private set; }
10 |
11 | public CopyOperation()
12 | {
13 |
14 | }
15 |
16 | public CopyOperation(JsonPointer path, JsonPointer fromPath) : base(path)
17 | {
18 | FromPath = fromPath;
19 | }
20 |
21 | public override void Write(JsonWriter writer)
22 | {
23 | writer.WriteStartObject();
24 |
25 | WriteOp(writer, "copy");
26 | WritePath(writer, Path);
27 | WriteFromPath(writer, FromPath);
28 |
29 | writer.WriteEndObject();
30 | }
31 |
32 | public override void Read(JObject jOperation)
33 | {
34 | Path = new JsonPointer(SplitPath((string)jOperation.GetValue("path")));
35 | FromPath = new JsonPointer(SplitPath((string)jOperation.GetValue("from")));
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/JsonDiffPatch/MoveOperation.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using Tavis;
6 |
7 | namespace JsonDiffPatch
8 | {
9 | public class MoveOperation : Operation
10 | {
11 | public JsonPointer FromPath { get; private set; }
12 |
13 | public MoveOperation()
14 | {
15 |
16 | }
17 |
18 | public MoveOperation(JsonPointer path, JsonPointer fromPath) : base(path)
19 | {
20 | FromPath = fromPath;
21 | }
22 |
23 | public override void Write(JsonWriter writer)
24 | {
25 | writer.WriteStartObject();
26 |
27 | WriteOp(writer, "move");
28 | WritePath(writer, Path);
29 | WriteFromPath(writer, FromPath);
30 |
31 | writer.WriteEndObject();
32 | }
33 |
34 | public override void Read(JObject jOperation)
35 | {
36 | Path = new JsonPointer(SplitPath((string)jOperation.GetValue("path")));
37 | FromPath = new JsonPointer(SplitPath((string)jOperation.GetValue("from")));
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/DiffTests2.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using JsonDiffPatch;
3 | using Newtonsoft.Json.Linq;
4 | using NUnit.Framework;
5 |
6 | namespace Tavis.JsonPatch.Tests
7 | {
8 | public class DiffTests2 {
9 |
10 | [Test]
11 | public void ComplexExampleWithDeepArrayChange()
12 | {
13 |
14 | var leftPath = @".\samples\scene{0}a.json";
15 | var rightPath = @".\samples\scene{0}b.json";
16 | var i = 1;
17 | while(File.Exists(string.Format(leftPath, i)))
18 | {
19 | var scene1Text = File.ReadAllText(string.Format(leftPath, i));
20 | var scene1 = JToken.Parse(scene1Text);
21 | var scene2Text = File.ReadAllText(string.Format(rightPath, i));
22 | var scene2 = JToken.Parse(scene2Text);
23 | var patchDoc = new JsonDiffer().Diff(scene1, scene2, true);
24 | //Assert.AreEqual("[{\"op\":\"remove\",\"path\":\"/items/0/entities/1\"}]",
25 | var patcher = new JsonPatcher();
26 | patcher.Patch(ref scene1, patchDoc);
27 | Assert.True(JToken.DeepEquals(scene1, scene2));
28 | i++;
29 | }
30 | }
31 |
32 |
33 | }
34 | }
--------------------------------------------------------------------------------
/JsonDiffPatchTests/RemoveTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using JsonDiffPatch;
3 | using NUnit.Framework;
4 |
5 | namespace Tavis.JsonPatch.Tests
6 | {
7 | public class RemoveTests
8 | {
9 | [Test]
10 | public void Remove_a_property()
11 | {
12 |
13 | var sample = PatchTests.GetSample2();
14 |
15 | var patchDocument = new PatchDocument();
16 | var pointer = new JsonPointer("/books/0/author");
17 |
18 | patchDocument.AddOperation(new RemoveOperation(pointer));
19 |
20 | new JsonPatcher().Patch(ref sample, patchDocument);
21 |
22 | Assert.Throws(typeof(ArgumentException), () => { pointer.Find(sample); });
23 | }
24 |
25 | [Test]
26 | public void Remove_an_array_element()
27 | {
28 |
29 | var sample = PatchTests.GetSample2();
30 |
31 | var patchDocument = new PatchDocument();
32 | var pointer = new JsonPointer("/books/0");
33 |
34 | patchDocument.AddOperation(new RemoveOperation(pointer));
35 |
36 | var patcher = new JsonPatcher();
37 | patcher.Patch(ref sample, patchDocument);
38 |
39 | Assert.Throws(typeof(ArgumentException), () =>
40 | {
41 | var x = pointer.Find("/books/1");
42 | });
43 |
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/PatchTests2.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using JsonDiffPatch;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using NUnit.Framework;
6 |
7 | namespace Tavis.JsonPatch.Tests
8 | {
9 | [TestFixture]
10 | public class PatchTests2
11 | {
12 | [TestCase("{a:1, b:2, c:3}",
13 | "[{\"op\":\"remove\",\"path\":\"/c\"}]",
14 | ExpectedResult = "{\"a\":1,\"b\":2}",
15 | TestName = "Patch can remove works for a simple value")]
16 | [TestCase("{a:1, b:2, c:3}",
17 | "[{\"op\":\"replace\",\"path\":\"\",value:{\"x\":0}}]",
18 | ExpectedResult = "{\"x\":0}",
19 | TestName = "Can patch the root object")]
20 | [TestCase("{\"\":1 }",
21 | "[{\"op\":\"replace\",\"path\":\"/\",value:{\"x\":0}}]",
22 | ExpectedResult = "{\"\":{\"x\":0}}",
23 | TestName = "Can patch a space named property")]
24 |
25 | public string JsonPatchesWorks(string leftString, string patchString)
26 | {
27 | var left = JToken.Parse(leftString);
28 | var patchDoc = PatchDocument.Parse(patchString);
29 | var patcher = new JsonPatcher();
30 | patcher.Patch(ref left, patchDoc);
31 |
32 |
33 | var patchJson = left.ToString(Formatting.None);
34 | return patchJson;
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/JsonDiffPatchTests/ReplaceTests.cs:
--------------------------------------------------------------------------------
1 | using JsonDiffPatch;
2 | using Newtonsoft.Json.Linq;
3 | using NUnit.Framework;
4 |
5 | namespace Tavis.JsonPatch.Tests
6 | {
7 | public class ReplaceTests
8 | {
9 | [Test]
10 | public void Replace_a_property_value_with_a_new_value()
11 | {
12 |
13 | var sample = PatchTests.GetSample2();
14 |
15 | var patchDocument = new PatchDocument();
16 | var pointer = new JsonPointer("/books/0/author");
17 |
18 | patchDocument.AddOperation(new ReplaceOperation(pointer, new JValue("Bob Brown")));
19 |
20 | new JsonPatcher().Patch(ref sample, patchDocument);
21 |
22 | Assert.AreEqual("Bob Brown", (string)pointer.Find(sample));
23 | }
24 |
25 | [Test]
26 | public void Replace_a_property_value_with_an_object()
27 | {
28 |
29 | var sample = PatchTests.GetSample2();
30 |
31 | var patchDocument = new PatchDocument();
32 | var pointer = new JsonPointer("/books/0/author");
33 |
34 | patchDocument.AddOperation(new ReplaceOperation(pointer, new JObject(new[] { new JProperty("hello", "world") })));
35 |
36 | new JsonPatcher().Patch(ref sample, patchDocument);
37 |
38 | var newPointer = new JsonPointer("/books/0/author/hello");
39 | Assert.AreEqual("world", (string)newPointer.Find(sample));
40 | }
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/MoveTests.cs:
--------------------------------------------------------------------------------
1 | using JsonDiffPatch;
2 | using Newtonsoft.Json.Linq;
3 | using NUnit.Framework;
4 |
5 | namespace Tavis.JsonPatch.Tests
6 | {
7 | public class MoveTests
8 | {
9 |
10 | [Test]
11 | public void Move_property()
12 | {
13 | var sample = PatchTests.GetSample2();
14 |
15 | var patchDocument = new PatchDocument();
16 | var frompointer = new JsonPointer("/books/0/author");
17 | var topointer = new JsonPointer("/books/1/author");
18 |
19 | patchDocument.AddOperation(new MoveOperation(topointer, frompointer));
20 |
21 | var patcher = new JsonPatcher();
22 | patcher.Patch(ref sample, patchDocument);
23 |
24 |
25 | var result = (string)topointer.Find(sample);
26 | Assert.AreEqual("F. Scott Fitzgerald", result);
27 | }
28 |
29 | [Test]
30 | public void Move_array_element()
31 | {
32 | var sample = PatchTests.GetSample2();
33 |
34 | var patchDocument = new PatchDocument();
35 | var frompointer = new JsonPointer("/books/1");
36 | var topointer = new JsonPointer("/books/0/child");
37 |
38 | patchDocument.AddOperation(new MoveOperation(topointer, frompointer));
39 |
40 | var patcher = new JsonPatcher();
41 | patcher.Patch(ref sample, patchDocument);
42 |
43 |
44 | var result = topointer.Find(sample);
45 | Assert.IsInstanceOf(typeof(JObject), result);
46 | }
47 |
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/JsonDiffPatch.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26403.7
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonDiffPatch", "JsonDiffPatch\JsonDiffPatch.csproj", "{F28B8328-85BE-4048-AA16-0C069095FB1F}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonDiffPatch.Tests", "JsonDiffPatchTests\JsonDiffPatch.Tests.csproj", "{1FCBC3F6-73A3-4686-9A8B-A7709CF1C198}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {F28B8328-85BE-4048-AA16-0C069095FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {F28B8328-85BE-4048-AA16-0C069095FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {F28B8328-85BE-4048-AA16-0C069095FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {F28B8328-85BE-4048-AA16-0C069095FB1F}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {1FCBC3F6-73A3-4686-9A8B-A7709CF1C198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {1FCBC3F6-73A3-4686-9A8B-A7709CF1C198}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {1FCBC3F6-73A3-4686-9A8B-A7709CF1C198}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {1FCBC3F6-73A3-4686-9A8B-A7709CF1C198}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/JsonDiffPatch.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net462
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/CopyTests.cs:
--------------------------------------------------------------------------------
1 | using JsonDiffPatch;
2 | using Newtonsoft.Json.Linq;
3 | using NUnit.Framework;
4 |
5 | namespace Tavis.JsonPatch.Tests
6 | {
7 | public class CopyTests
8 | {
9 | [Test]
10 | public void Copy_array_element()
11 | {
12 | var sample = PatchTests.GetSample2();
13 |
14 | var patchDocument = new PatchDocument();
15 | var frompointer = new JsonPointer("/books/0");
16 | var topointer = new JsonPointer("/books/-");
17 |
18 | patchDocument.AddOperation(new CopyOperation(topointer, frompointer));
19 |
20 | var patcher = new JsonPatcher();
21 | patcher.Patch(ref sample, patchDocument);
22 |
23 | var result = new JsonPointer("/books/2").Find(sample);
24 | Assert.IsInstanceOf(typeof(JObject), result);
25 |
26 | }
27 |
28 | [Test]
29 | public void Copy_property()
30 | {
31 | var sample = PatchTests.GetSample2();
32 |
33 | var patchDocument = new PatchDocument();
34 | var frompointer = new JsonPointer("/books/0/ISBN");
35 | var topointer = new JsonPointer("/books/1/ISBN");
36 |
37 | patchDocument.AddOperation(new AddOperation(frompointer, new JValue("21123123")));
38 | patchDocument.AddOperation(new CopyOperation(topointer, frompointer));
39 |
40 | var patcher = new JsonPatcher();
41 | patcher.Patch(ref sample, patchDocument);
42 |
43 | var result = new JsonPointer("/books/1/ISBN").Find(sample);
44 | Assert.AreEqual("21123123", result.Value());
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/JsonDiffPatch/Operation.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 | using Tavis;
5 |
6 | namespace JsonDiffPatch
7 | {
8 | public abstract class Operation
9 | {
10 | public JsonPointer Path { get; protected set; }
11 |
12 | public Operation()
13 | {
14 |
15 | }
16 |
17 | public Operation(JsonPointer path)
18 | {
19 | Path = path;
20 | }
21 |
22 | public abstract void Write(JsonWriter writer);
23 |
24 | protected static void WriteOp(JsonWriter writer, string op)
25 | {
26 | writer.WritePropertyName("op");
27 | writer.WriteValue(op);
28 | }
29 |
30 | protected static void WritePath(JsonWriter writer, JsonPointer pointer)
31 | {
32 | writer.WritePropertyName("path");
33 | writer.WriteValue(pointer.ToString());
34 | }
35 |
36 | protected static void WriteFromPath(JsonWriter writer, JsonPointer pointer)
37 | {
38 | writer.WritePropertyName("from");
39 | writer.WriteValue(pointer.ToString());
40 | }
41 | protected static void WriteValue(JsonWriter writer, JToken value)
42 | {
43 | writer.WritePropertyName("value");
44 | value.WriteTo(writer);
45 | }
46 |
47 | protected static string[] SplitPath(string path) => path.Split('/').Skip(1).ToArray();
48 |
49 | public abstract void Read(JObject jOperation);
50 |
51 | public static Operation Parse(string json)
52 | {
53 | return Build(JObject.Parse(json));
54 | }
55 |
56 | public static Operation Build(JObject jOperation)
57 | {
58 | var op = PatchDocument.CreateOperation((string)jOperation["op"]);
59 | op.Read(jOperation);
60 | return op;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/JsonDiffPatch/AbstractPatcher.cs:
--------------------------------------------------------------------------------
1 | namespace JsonDiffPatch
2 | {
3 | public abstract class AbstractPatcher where TDoc : class
4 | {
5 |
6 | ///
7 | /// Apply the patch document to the target
8 | ///
9 | /// has to be a ref, as the object may be replaced with something quite different for remove operations that apply to the root
10 | public virtual void Patch(ref TDoc target, PatchDocument document)
11 | {
12 | foreach (var operation in document.Operations)
13 | {
14 | target = ApplyOperation(operation, target);
15 | }
16 | }
17 |
18 | ///
19 | /// return value indicates that a new
20 | ///
21 | ///
22 | ///
23 | /// a new root document, or null if one is not needed
24 | public virtual TDoc ApplyOperation(Operation operation, TDoc target)
25 | {
26 | if (operation is AddOperation) Add((AddOperation)operation, target);
27 | else if (operation is CopyOperation) Copy((CopyOperation)operation, target);
28 | else if (operation is MoveOperation) Move((MoveOperation)operation, target);
29 | else if (operation is RemoveOperation) Remove((RemoveOperation)operation, target);
30 | else if (operation is ReplaceOperation) target = Replace((ReplaceOperation)operation, target) ?? target;
31 | else if (operation is TestOperation) Test((TestOperation)operation, target);
32 | return target;
33 | }
34 |
35 | protected abstract void Add(AddOperation operation, TDoc target);
36 | protected abstract void Copy(CopyOperation operation, TDoc target);
37 | protected abstract void Move(MoveOperation operation, TDoc target);
38 | protected abstract void Remove(RemoveOperation operation, TDoc target);
39 | protected abstract TDoc Replace(ReplaceOperation operation, TDoc target);
40 | protected abstract void Test(TestOperation operation, TDoc target);
41 |
42 | }
43 | }
--------------------------------------------------------------------------------
/JsonDiffPatch/Tavis.JsonPointer/JsonPointer.cs:
--------------------------------------------------------------------------------
1 | //see https://github.com/tavis-software/Tavis.JsonPointer
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using Newtonsoft.Json.Linq;
7 |
8 | namespace Tavis
9 | {
10 | public class JsonPointer
11 | {
12 | private readonly IReadOnlyList _Tokens;
13 |
14 | public JsonPointer(string pointer)
15 | {
16 | _Tokens = pointer.Split('/').Skip(1).Select(Decode).ToArray();
17 | }
18 |
19 | internal JsonPointer(IReadOnlyList tokens)
20 | {
21 | _Tokens = tokens;
22 | }
23 | private string Decode(string token)
24 | {
25 | return Uri.UnescapeDataString(token).Replace("~1", "/").Replace("~0", "~");
26 | }
27 |
28 | public bool IsNewPointer()
29 | {
30 | return _Tokens[_Tokens.Count - 1] == "-";
31 | }
32 |
33 | public JsonPointer ParentPointer
34 | {
35 | get
36 | {
37 | if (_Tokens.Count == 0) return null;
38 |
39 | var tokens = new string[_Tokens.Count - 1];
40 | for (int i = 0; i < _Tokens.Count - 1; i++)
41 | {
42 | tokens[i] = _Tokens[i];
43 | }
44 |
45 | return new JsonPointer(tokens);
46 | }
47 | }
48 |
49 | public JToken Find(JToken sample)
50 | {
51 | if (_Tokens.Count == 0)
52 | {
53 | return sample;
54 | }
55 | try
56 | {
57 | var pointer = sample;
58 | foreach (var token in _Tokens.Select(t => t.Replace("~1", "/").Replace("~0", "~")))
59 | {
60 | if (pointer is JArray)
61 | {
62 | pointer = pointer[Convert.ToInt32(token)];
63 | }
64 | else
65 | {
66 | pointer = pointer[token];
67 | if (pointer == null)
68 | {
69 | throw new ArgumentException("Cannot find " + token);
70 | }
71 |
72 | }
73 | }
74 | return pointer;
75 | }
76 | catch (Exception ex)
77 | {
78 | throw new ArgumentException("Failed to dereference pointer",ex);
79 | }
80 | }
81 |
82 | public override string ToString()
83 | {
84 | return "/" + String.Join("/", _Tokens);
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # JsonDiffPatch
2 |
3 |
4 |
5 | This library is an implementation of a Json Patch [RFC 6902](http://tools.ietf.org/html/rfc6902).
6 | * forked from https://github.com/tavis-software/Tavis.JsonPatch
7 | * plus a modified diff generator by Ian Mercer (http://blog.abodit.com/2014/05/json-patch-c-implementation/)
8 |
9 | The default example from the specification looks like this,
10 |
11 | [
12 | { "op": "test", "path": "/a/b/c", "value": "foo" },
13 | { "op": "remove", "path": "/a/b/c" },
14 | { "op": "add", "path": "/a/b/c", "value": ["foo", "bar"] },
15 | { "op": "replace", "path": "/a/b/c", "value": 42 },
16 | { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
17 | { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
18 | ]
19 |
20 | This library allows you to create this document by doing,
21 |
22 | var patchDoc = new PatchDocument( new Operation[]
23 | {
24 | new TestOperation() {Path = new JsonPointer("/a/b/c"), Value = new JValue("foo")},
25 | new RemoveOperation() {Path = new JsonPointer("/a/b/c") },
26 | new AddOperation() {Path = new JsonPointer("/a/b/c"), Value = new JArray(new JValue("foo"), new JValue("bar"))},
27 | new ReplaceOperation() {Path = new JsonPointer("/a/b/c"), Value = new JValue(42)},
28 | new MoveOperation() {FromPath = new JsonPointer("/a/b/c"), Path = new JsonPointer("/a/b/d") },
29 | new CopyOperation() {FromPath = new JsonPointer("/a/b/d"), Path = new JsonPointer("/a/b/e") },
30 | });
31 |
32 | This document can be serialized to the wire format like this,
33 |
34 | var outputstream = patchDoc.ToStream();
35 |
36 | You can also read patch documents from the wire representation and apply them to a JSON document.
37 |
38 | var targetDoc = JToken.Parse("{ 'foo': 'bar'}");
39 | var patchDoc = PatchDocument.Parse(@"[ { 'op': 'add', 'path': '/baz', 'value': 'qux' } ]");
40 |
41 | patchDoc.ApplyTo(targetDoc);
42 |
43 | You can also generate a patchdocument by diffing two json objects
44 |
45 | var left = JToken.Parse(leftString);
46 | var right = JToken.Parse(rightString);
47 | var patchDoc = new JsonDiffer().Diff(left, right);
48 |
49 | You can apply a patch document to a json object too.
50 |
51 | var left = JToken.Parse(leftString);
52 | var patcher = new JsonPatcher();
53 | patcher.Patch(ref left, patchDoc); //left is now updated by the patchDoc
54 |
55 |
56 |
57 | The unit tests provide examples of other usages.
58 |
59 | This library is a PCL based library and so will work on Windows 8, Windows Phone 7.5, .Net 4.
60 |
61 | A nuget package is available [here](http://www.nuget.org/packages/Tavis.JsonPatch).
62 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/PatchTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using JsonDiffPatch;
3 | using Newtonsoft.Json.Linq;
4 | using NUnit.Framework;
5 |
6 | namespace Tavis.JsonPatch.Tests
7 | {
8 | public class PatchTests
9 | {
10 | [Test]
11 | public void CreateEmptyPatch()
12 | {
13 |
14 | var sample = GetSample2();
15 | var sampletext = sample.ToString();
16 |
17 | var patchDocument = new PatchDocument();
18 | new JsonPatcher().Patch(ref sample, patchDocument);
19 |
20 | Assert.AreEqual(sampletext,sample.ToString());
21 | }
22 |
23 |
24 | [Test]
25 | public void LoadPatch1()
26 | {
27 | var names = this.GetType()
28 | .Assembly.GetManifestResourceNames();
29 | var patchDoc =
30 | PatchDocument.Load(this.GetType()
31 | .Assembly.GetManifestResourceStream("JsonDiffPatch.Tests.Samples.LoadTest1.json"));
32 |
33 | Assert.NotNull(patchDoc);
34 | Assert.AreEqual(6,patchDoc.Operations.Count);
35 | }
36 |
37 |
38 | [Test]
39 | public void TestExample1()
40 | {
41 | var targetDoc = JToken.Parse("{ 'foo': 'bar'}");
42 | var patchDoc = PatchDocument.Parse(@"[
43 | { 'op': 'add', 'path': '/baz', 'value': 'qux' }
44 | ]");
45 | new JsonPatcher().Patch(ref targetDoc, patchDoc);
46 |
47 |
48 | Assert.True(JToken.DeepEquals(JToken.Parse(@"{
49 | 'foo': 'bar',
50 | 'baz': 'qux'
51 | }"), targetDoc));
52 | }
53 |
54 |
55 |
56 |
57 | [Test]
58 | public void SerializePatchDocument()
59 | {
60 | var patchDoc = new PatchDocument(new Operation[]
61 | {
62 | new TestOperation(new JsonPointer("/a/b/c"), new JValue("foo")),
63 | new RemoveOperation(new JsonPointer("/a/b/c")),
64 | new AddOperation(new JsonPointer("/a/b/c"), new JArray(new JValue("foo"), new JValue("bar"))),
65 | new ReplaceOperation(new JsonPointer("/a/b/c"), new JValue(42)),
66 | new MoveOperation(new JsonPointer("/a/b/d"), new JsonPointer("/a/b/c")),
67 | new CopyOperation(new JsonPointer("/a/b/e"), new JsonPointer("/a/b/d")),
68 | });
69 |
70 | var outputstream = patchDoc.ToStream();
71 | var output = new StreamReader(outputstream).ReadToEnd();
72 |
73 | var jOutput = JToken.Parse(output);
74 |
75 | var jExpected = JToken.Parse(new StreamReader(this.GetType()
76 | .Assembly.GetManifestResourceStream("JsonDiffPatch.Tests.Samples.LoadTest1.json")).ReadToEnd());
77 | Assert.True(JToken.DeepEquals(jExpected,jOutput));
78 | }
79 |
80 |
81 |
82 | public static JToken GetSample2()
83 | {
84 | return JToken.Parse(@"{
85 | 'books': [
86 | {
87 | 'title' : 'The Great Gatsby',
88 | 'author' : 'F. Scott Fitzgerald'
89 | },
90 | {
91 | 'title' : 'The Grapes of Wrath',
92 | 'author' : 'John Steinbeck'
93 | }
94 | ]
95 | }");
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/JsonDiffPatchTests/AddTests.cs:
--------------------------------------------------------------------------------
1 | using JsonDiffPatch;
2 | using Newtonsoft.Json.Linq;
3 | using NUnit.Framework;
4 |
5 | namespace Tavis.JsonPatch.Tests
6 | {
7 | public class AddTests
8 | {
9 |
10 | [Test]
11 | public void Add_an_array_element()
12 | {
13 |
14 | var sample = PatchTests.GetSample2();
15 |
16 | var patchDocument = new PatchDocument();
17 | var pointer = new JsonPointer("/books");
18 |
19 | patchDocument.AddOperation(new AddOperation(pointer, new JObject(new[] { new JProperty("author", "James Brown") })));
20 |
21 | var patcher = new JsonPatcher();
22 | patcher.Patch(ref sample, patchDocument);
23 |
24 | var list = sample["books"] as JArray;
25 |
26 | Assert.AreEqual(3, list.Count);
27 |
28 | }
29 |
30 | [Test]
31 | public void Add_an_existing_member_property() // Why isn't this replace?
32 | {
33 |
34 | var sample = PatchTests.GetSample2();
35 |
36 | var patchDocument = new PatchDocument();
37 | var pointer = new JsonPointer("/books/0/title");
38 |
39 | patchDocument.AddOperation(new AddOperation(pointer, new JValue("Little Red Riding Hood")));
40 |
41 | var patcher = new JsonPatcher();
42 | patcher.Patch(ref sample, patchDocument);
43 |
44 |
45 | var result = (string)pointer.Find(sample);
46 | Assert.AreEqual("Little Red Riding Hood", result);
47 |
48 | }
49 |
50 | [Test]
51 | public void Add_a_non_existing_member_property()
52 | {
53 |
54 | var sample = PatchTests.GetSample2();
55 |
56 | var patchDocument = new PatchDocument();
57 | var pointer = new JsonPointer("/books/0/ISBN");
58 |
59 | patchDocument.AddOperation(new AddOperation(pointer, new JValue("213324234343")));
60 |
61 | var patcher = new JsonPatcher();
62 | patcher.Patch(ref sample, patchDocument);
63 |
64 |
65 | var result = (string)pointer.Find(sample);
66 | Assert.AreEqual("213324234343", result);
67 |
68 | }
69 |
70 | [Test]
71 | public void Add_a_non_existing_member_property_with_slash_character()
72 | {
73 |
74 | var sample = PatchTests.GetSample2();
75 |
76 | var patchDocument = new PatchDocument();
77 | var pointer = new JsonPointer("/books/0/b~1c");
78 |
79 | patchDocument.AddOperation(new AddOperation(pointer, new JValue("42")));
80 |
81 | var patcher = new JsonPatcher();
82 | patcher.Patch(ref sample, patchDocument);
83 |
84 |
85 | var result = (string)pointer.Find(sample);
86 | Assert.AreEqual("42", result);
87 |
88 | }
89 |
90 | [Test]
91 | public void Add_a_non_existing_member_property_with_tilda_character()
92 | {
93 |
94 | var sample = PatchTests.GetSample2();
95 |
96 | var patchDocument = new PatchDocument();
97 | var pointer = new JsonPointer("/books/0/b~0c");
98 |
99 | patchDocument.AddOperation(new AddOperation(pointer, new JValue("42")));
100 |
101 | var patcher = new JsonPatcher();
102 | patcher.Patch(ref sample, patchDocument);
103 |
104 |
105 | var result = (string)pointer.Find(sample);
106 | Assert.AreEqual("42", result);
107 |
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/JsonDiffPatch/ArrayLcs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace JsonDiffPatch
7 | {
8 | class ArrayLcs
9 | {
10 | private readonly Func