static JsonDocumentBuilder _FromDiff(JsonElement source, JsonElement target) { JsonElementEqualityComparer comparer = JsonElementEqualityComparer.Instance; if (source.ValueKind != JsonValueKind.Object || target.ValueKind != JsonValueKind.Object) { return(new JsonDocumentBuilder(target)); } var builder = new JsonDocumentBuilder(JsonValueKind.Object); foreach (var property in source.EnumerateObject()) { JsonElement value; if (target.TryGetProperty(property.Name, out value)) { if (!comparer.Equals(property.Value, value)) { builder.AddProperty(property.Name, _FromDiff(property.Value, value)); } } else { builder.AddProperty(property.Name, new JsonDocumentBuilder(JsonValueKind.Null)); } } foreach (var property in target.EnumerateObject()) { JsonElement value; if (!source.TryGetProperty(property.Name, out value)) { builder.AddProperty(property.Name, new JsonDocumentBuilder(property.Value)); } } return(builder); }
static JsonDocumentBuilder _FromDiff(JsonElement source, JsonElement target, string path) { var builder = new JsonDocumentBuilder(JsonValueKind.Array); JsonElementEqualityComparer comparer = JsonElementEqualityComparer.Instance; if (comparer.Equals(source, target)) { return(builder); } if (source.ValueKind == JsonValueKind.Array && target.ValueKind == JsonValueKind.Array) { int common = Math.Min(source.GetArrayLength(), target.GetArrayLength()); for (int i = 0; i < common; ++i) { var buffer = new StringBuilder(path); buffer.Append("/"); buffer.Append(i.ToString()); var temp_diff = _FromDiff(source[i], target[i], buffer.ToString()); foreach (var item in temp_diff.EnumerateArray()) { builder.AddArrayItem(item); } } // Element in source, not in target - remove for (int i = source.GetArrayLength(); i-- > target.GetArrayLength();) { var buffer = new StringBuilder(path); buffer.Append("/"); buffer.Append(i.ToString()); var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object); valBuilder.AddProperty("op", new JsonDocumentBuilder("remove")); valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString())); builder.AddArrayItem(valBuilder); } // Element in target, not in source - add, for (int i = source.GetArrayLength(); i < target.GetArrayLength(); ++i) { var a = target[i]; var buffer = new StringBuilder(path); buffer.Append("/"); buffer.Append(i.ToString()); var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object); valBuilder.AddProperty("op", new JsonDocumentBuilder("add")); valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString())); valBuilder.AddProperty("value", new JsonDocumentBuilder(a)); builder.AddArrayItem(valBuilder); } } else if (source.ValueKind == JsonValueKind.Object && target.ValueKind == JsonValueKind.Object) { foreach (var a in source.EnumerateObject()) { var buffer = new StringBuilder(path); buffer.Append("/"); buffer.Append(JsonPointer.Escape(a.Name)); JsonElement element; if (target.TryGetProperty(a.Name, out element)) { var temp_diff = _FromDiff(a.Value, element, buffer.ToString()); foreach (var item in temp_diff.EnumerateArray()) { builder.AddArrayItem(item); } } else { var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object); valBuilder.AddProperty("op", new JsonDocumentBuilder("remove")); valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString())); builder.AddArrayItem(valBuilder); } } foreach (var a in target.EnumerateObject()) { JsonElement element; if (!source.TryGetProperty(a.Name, out element)) { var buffer = new StringBuilder(path); buffer.Append("/"); buffer.Append(JsonPointer.Escape(a.Name)); var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object); valBuilder.AddProperty("op", new JsonDocumentBuilder("add")); valBuilder.AddProperty("path", new JsonDocumentBuilder(buffer.ToString())); valBuilder.AddProperty("value", new JsonDocumentBuilder(a.Value)); builder.AddArrayItem(valBuilder); } } } else { var valBuilder = new JsonDocumentBuilder(JsonValueKind.Object); valBuilder.AddProperty("op", new JsonDocumentBuilder("replace")); valBuilder.AddProperty("path", new JsonDocumentBuilder(path)); valBuilder.AddProperty("value", new JsonDocumentBuilder(target)); builder.AddArrayItem(valBuilder); } return(builder); }
static void ApplyPatch(ref JsonDocumentBuilder target, JsonElement patch) { JsonElementEqualityComparer comparer = JsonElementEqualityComparer.Instance; Debug.Assert(target != null); if (patch.ValueKind != JsonValueKind.Array) { throw new ArgumentException("Patch must be an array"); } foreach (var operation in patch.EnumerateArray()) { JsonElement opElement; if (!operation.TryGetProperty("op", out opElement)) { throw new ArgumentException("Invalid patch"); } string op = opElement.GetString() ?? throw new InvalidOperationException("Operation cannot be null"); JsonElement pathElement; if (!operation.TryGetProperty("path", out pathElement)) { throw new ArgumentException(op, "Invalid patch"); } string path = pathElement.GetString() ?? throw new InvalidOperationException("Operation cannot be null");; JsonPointer location; if (!JsonPointer.TryParse(path, out location)) { throw new ArgumentException(op, "Invalid patch"); } if (op == "test") { JsonElement value; if (!operation.TryGetProperty("value", out value)) { throw new ArgumentException(op, "Invalid patch"); } JsonDocumentBuilder tested; if (!location.TryGetValue(target, out tested)) { throw new ArgumentException(op, "Invalid patch"); } using (var doc = tested.ToJsonDocument()) { if (!comparer.Equals(doc.RootElement, value)) { throw new JsonPatchException(op, "Test failed"); } } } else if (op == "add") { JsonElement value; if (!operation.TryGetProperty("value", out value)) { throw new ArgumentException(op, "Invalid patch"); } var valueBuilder = new JsonDocumentBuilder(value); if (!location.TryAddIfAbsent(ref target, valueBuilder)) // try insert without replace { if (!location.TryReplace(ref target, valueBuilder)) // try insert without replace { throw new JsonPatchException(op, "Add failed"); } } } else if (op == "remove") { if (!location.TryRemove(ref target)) { throw new JsonPatchException(op, "Add failed"); } } else if (op == "replace") { JsonElement value; if (!operation.TryGetProperty("value", out value)) { throw new ArgumentException(op, "Invalid patch"); } var valueBuilder = new JsonDocumentBuilder(value); if (!location.TryReplace(ref target, valueBuilder)) { throw new JsonPatchException(op, "Replace failed"); } } else if (op == "move") { JsonElement fromElement; if (!operation.TryGetProperty("from", out fromElement)) { throw new ArgumentException(op, "Invalid patch"); } string from = fromElement.GetString() ?? throw new InvalidOperationException("From element cannot be null");; JsonPointer fromPointer; if (!JsonPointer.TryParse(from, out fromPointer)) { throw new ArgumentException(op, "Invalid patch"); } JsonDocumentBuilder value; if (!fromPointer.TryGetValue(target, out value)) { throw new JsonPatchException(op, "Move failed"); } if (!fromPointer.TryRemove(ref target)) { throw new JsonPatchException(op, "Move failed"); } if (!location.TryAddIfAbsent(ref target, value)) { if (!location.TryReplace(ref target, value)) // try insert without replace { throw new JsonPatchException(op, "Move failed"); } } } else if (op == "copy") { JsonElement fromElement; if (!operation.TryGetProperty("from", out fromElement)) { throw new ArgumentException(op, "Invalid patch"); } string from = fromElement.GetString() ?? throw new InvalidOperationException("from cannot be null"); JsonPointer fromPointer; if (!JsonPointer.TryParse(from, out fromPointer)) { throw new ArgumentException(op, "Invalid patch"); } JsonDocumentBuilder value; if (!fromPointer.TryGetValue(target, out value)) { throw new JsonPatchException(op, "Copy failed"); } if (!location.TryAddIfAbsent(ref target, value)) { if (!location.TryReplace(ref target, value)) // try insert without replace { throw new JsonPatchException(op, "Move failed"); } } } } }