public static JsonPointer ToDefinitePath(this JsonPointer pointer, JsonDocumentBuilder value) { if (value.ValueKind == JsonValueKind.Array && pointer.Tokens.Count > 0 && pointer.Tokens[pointer.Tokens.Count - 1] == "-") { var tokens = new List <string>(); for (int i = 0; i < pointer.Tokens.Count - 1; ++i) { tokens.Add(pointer.Tokens[i]); } tokens.Add(value.GetArrayLength().ToString()); return(new JsonPointer(tokens)); } else { return(pointer); } }
/// <summary> /// Determines whether two JSONPointer objects have the same value. /// </summary> /// <param name="other"></param> /// <returns><c>true</c> if other is a <see cref="JsonPointer"/> and has exactly the same reference tokens as this instance; otherwise, <c>false</c>. /// If other is <c>null</c>, the method returns <c>false</c>.</returns> public bool Equals(JsonPointer other) { if (other == null) { return(false); } if (Tokens.Count != other.Tokens.Count) { return(false); } for (int i = 0; i < Tokens.Count; ++i) { if (!Tokens[i].Equals(other.Tokens[i])) { return(false); } } return(true); }
/// <summary> /// Parses a JSON Pointer represented as a string value or a /// fragment identifier (starts with <c>#</c>) into a <see cref="JsonPointer"/>. /// </summary> /// <param name="input">A JSON Pointer represented as a string or a fragment identifier.</param> /// <param name="pointer">The JsonPointer.</param> /// <returns><c>true</c> if the input string can be parsed into a list of reference tokens, <c>false</c> otherwise.</returns> /// <exception cref="ArgumentNullException"> /// The <paramref name="input"/> is <see langword="null"/>. /// </exception> public static bool TryParse(string input, out JsonPointer pointer) { if (input == null) { throw new ArgumentNullException(nameof(input)); } var tokens = new List <string>(); if (input.Length == 0 || input.Equals("#")) { pointer = new JsonPointer(tokens); return(true); } JsonPointerState state = JsonPointerState.Start; int index = 0; var buffer = new StringBuilder(); if (input[0] == '#') { input = Uri.UnescapeDataString(input); index = 1; } while (index < input.Length) { bool done = false; while (index < input.Length && !done) { switch (state) { case JsonPointerState.Start: switch (input[index]) { case '/': state = JsonPointerState.Delim; break; default: pointer = JsonPointer.Default; return(false); } ; break; case JsonPointerState.Delim: switch (input[index]) { case '/': done = true; break; case '~': state = JsonPointerState.Escaped; break; default: buffer.Append(input[index]); break; } ; break; case JsonPointerState.Escaped: switch (input[index]) { case '0': buffer.Append('~'); state = JsonPointerState.Delim; break; case '1': buffer.Append('/'); state = JsonPointerState.Delim; break; default: pointer = JsonPointer.Default; return(false); } ; break; default: pointer = JsonPointer.Default; return(false); } ++index; } tokens.Add(buffer.ToString()); buffer.Clear(); } if (buffer.Length > 0) { tokens.Add(buffer.ToString()); } pointer = new JsonPointer(tokens); return(true); }
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"); } } } } }
public static bool TryAdd(this JsonPointer location, ref JsonDocumentBuilder root, JsonDocumentBuilder value) { JsonDocumentBuilder current = root; string token = ""; var enumerator = location.GetEnumerator(); bool more = enumerator.MoveNext(); if (!more) { return(false); } while (more) { token = enumerator.Current; more = enumerator.MoveNext(); if (more) { if (!TryResolve(token, current, out current)) { return(false); } } } if (current.ValueKind == JsonValueKind.Array) { if (token.Length == 1 && token[0] == '-') { current.AddArrayItem(value); current = current[current.GetArrayLength() - 1]; } else { int index; if (!int.TryParse(token, out index)) { return(false); } if (index > current.GetArrayLength()) { return(false); } if (index == current.GetArrayLength()) { current.AddArrayItem(value); current = value; } else { current.InsertArrayItem(index, value); current = value; } } } else if (current.ValueKind == JsonValueKind.Object) { if (current.ContainsPropertyName(token)) { current.RemoveProperty(token); } current.AddProperty(token, value); current = value; } else { return(false); } return(true); }
static bool TryUnflattenArray(JsonElement value, out JsonDocumentBuilder result) { if (value.ValueKind != JsonValueKind.Object) { throw new ArgumentException("The value to unflatten is not a JSON object"); } result = new JsonDocumentBuilder(JsonValueKind.Object); foreach (var item in value.EnumerateObject()) { JsonDocumentBuilder? parent = null; JsonDocumentBuilder part = result; int parentIndex = 0; string parentName = ""; JsonPointer ptr; if (!JsonPointer.TryParse(item.Name, out ptr)) { throw new ArgumentException("Name contains invalid JSON Pointer"); } int index = 0; var it = ptr.GetEnumerator(); bool more = it.MoveNext(); while (more) { string token = it.Current; int n; if (int.TryParse(token, out n) && index++ == n) { if (part.ValueKind != JsonValueKind.Array) { if (parent != null && parent.ValueKind == JsonValueKind.Object) { parent.RemoveProperty(parentName); var val = new JsonDocumentBuilder(JsonValueKind.Array); parent.AddProperty(parentName, val); part = val; } else if (parent != null && parent.ValueKind == JsonValueKind.Array) { var val = new JsonDocumentBuilder(JsonValueKind.Array); parent[parentIndex] = val; part = val; } else { return false; } } parent = part; parentIndex = n; parentName = token; more = it.MoveNext(); if (more) { if (n >= part.GetArrayLength()) { part.AddArrayItem(new JsonDocumentBuilder(JsonValueKind.Object)); part = part[part.GetArrayLength() - 1]; } else { part = part[n]; } } else { part.AddArrayItem(new JsonDocumentBuilder(item.Value)); part = part[part.GetArrayLength() - 1]; } } else if (part.ValueKind == JsonValueKind.Object) { more = it.MoveNext(); if (more) { JsonDocumentBuilder val; if (part.TryGetProperty(token, out val)) { part = val; } else { val = new JsonDocumentBuilder(JsonValueKind.Object); part.AddProperty(token,val); part = val; } } else { JsonDocumentBuilder val; if (part.TryGetProperty(token, out val)) { part = val; } else { val = new JsonDocumentBuilder(item.Value); part.AddProperty(token,val); part = val; } } } else { return false; } } } return true; }