/// <summary> /// Converts a JSON object or array into a single depth JSON object of name-value pairs, /// such that the names are JSON Pointer strings, and the values are either string, /// number, true, false, null, empty object, or empty array. /// </summary> /// <remarks> /// It is the users responsibilty to properly Dispose the returned <see cref="JsonDocument"/> value /// </remarks> /// <param name="value">The value to be flattened.</param> /// <returns>The flattened value</returns> public static JsonDocument Flatten(JsonElement value) { var result = new JsonDocumentBuilder(JsonValueKind.Object); string parentKey = ""; _Flatten(parentKey, value, result); return result.ToJsonDocument(); }
public static bool TryResolve(string token, JsonDocumentBuilder current, out JsonDocumentBuilder result) { result = current; if (result.ValueKind == JsonValueKind.Array) { if (token == "-") { return(false); } int index = 0; if (!int.TryParse(token, out index)) { return(false); } if (index >= result.GetArrayLength()) { return(false); } result = result[index]; } else if (result.ValueKind == JsonValueKind.Object) { if (!result.TryGetProperty(token, out result)) { return(false); } } else { return(false); } return(true); }
static JsonDocumentBuilder ApplyMergePatch(ref JsonDocumentBuilder target, JsonElement patch) { if (patch.ValueKind == JsonValueKind.Object) { if (target.ValueKind != JsonValueKind.Object) { target = new JsonDocumentBuilder(JsonValueKind.Object); } foreach (var property in patch.EnumerateObject()) { JsonDocumentBuilder item; if (target.TryGetProperty(property.Name, out item)) { target.RemoveProperty(property.Name); if (property.Value.ValueKind != JsonValueKind.Null) { target.AddProperty(property.Name, ApplyMergePatch(ref item, property.Value)); } } else if (property.Value.ValueKind != JsonValueKind.Null) { item = new JsonDocumentBuilder(JsonValueKind.Object); target.AddProperty(property.Name, ApplyMergePatch(ref item, property.Value)); } } return(target); } else { return(new JsonDocumentBuilder(patch)); } }
/// <summary> /// Applies a JSON Merge Patch as defined in <see href="https://datatracker.ietf.org/doc/html/rfc7396">RFC 7396</see> /// to a source JSON value. /// </summary> /// <remarks> /// It is the users responsibilty to properly Dispose the returned <see cref="JsonDocument"/> value /// </remarks> /// <param name="source">The source JSON value.</param> /// <param name="patch">The JSON merge patch to be applied to the source JSON value.</param> /// <returns>The patched JSON value</returns> public static JsonDocument ApplyMergePatch(JsonElement source, JsonElement patch) { var documentBuilder = new JsonDocumentBuilder(source); var builder = ApplyMergePatch(ref documentBuilder, patch); return(builder.ToJsonDocument()); }
public static bool TryRemove(this JsonPointer location, ref JsonDocumentBuilder root) { 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] == '-') { return(false); } else { int index; if (!int.TryParse(token, out index)) { return(false); } if (index >= current.GetArrayLength()) { return(false); } current.RemoveArrayItemAt(index); } } else if (current.ValueKind == JsonValueKind.Object) { if (current.ContainsPropertyName(token)) { current.RemoveProperty(token); } } else { return(false); } return(true); }
internal bool TryAddProperty(string name, JsonDocumentBuilder value) { if (ValueKind != JsonValueKind.Object) { throw new InvalidOperationException("This value's ValueKind is not Object."); } return(GetDictionary().TryAdd(name, value)); }
internal void AddArrayItem(JsonDocumentBuilder value) { if (ValueKind != JsonValueKind.Array) { throw new InvalidOperationException("This value's ValueKind is not Array."); } GetList().Add(value); }
internal void InsertArrayItem(int index, JsonDocumentBuilder value) { if (ValueKind != JsonValueKind.Array) { throw new InvalidOperationException("This value's ValueKind is not Array."); } GetList().Insert(index, value); }
static JsonDocumentBuilder UnflattenToObject(JsonElement value, IntegerTokenUnflattening options = IntegerTokenUnflattening.TryIndex) { if (value.ValueKind != JsonValueKind.Object) { throw new ArgumentException("The value to unflatten is not a JSON object"); } var result = new JsonDocumentBuilder(JsonValueKind.Object); foreach (var item in value.EnumerateObject()) { JsonDocumentBuilder part = result; JsonPointer ptr; if (!JsonPointer.TryParse(item.Name, out ptr)) { throw new ArgumentException("Name contains invalid JSON Pointer"); } var it = ptr.GetEnumerator(); bool more = it.MoveNext(); while (more) { var s = it.Current; more = it.MoveNext(); if (more) { JsonDocumentBuilder val; if (part.TryGetProperty(s, out val)) { part = val; } else { val = new JsonDocumentBuilder(JsonValueKind.Object); part.AddProperty(s,val); part = val; } } else { JsonDocumentBuilder val; if (part.TryGetProperty(s, out val)) { part = val; } else { val = new JsonDocumentBuilder(item.Value); part.AddProperty(s,val); part = val; } } } } return options == IntegerTokenUnflattening.TryIndex ? SafeUnflatten (result) : result; }
internal bool TryGetProperty(string name, out JsonDocumentBuilder value) { if (ValueKind != JsonValueKind.Object) { throw new InvalidOperationException("This value's ValueKind is not Object."); } if (ValueKind != JsonValueKind.Object) { value = JsonDocumentBuilder.Default; return(false); } return(GetDictionary().TryGetValue(name, out value)); }
static void _Flatten(string parentKey, JsonElement parentValue, JsonDocumentBuilder result) { switch (parentValue.ValueKind) { case JsonValueKind.Array: { if (parentValue.GetArrayLength() == 0) { result.AddProperty(parentKey, new JsonDocumentBuilder(parentValue)); } else { for (int i = 0; i < parentValue.GetArrayLength(); ++i) { var buffer = new StringBuilder(parentKey); buffer.Append('/'); buffer.Append(i.ToString()); _Flatten(buffer.ToString(), parentValue[i], result); } } break; } case JsonValueKind.Object: { if (parentValue.EnumerateObject().Count() == 0) { result.AddProperty(parentKey, new JsonDocumentBuilder(parentValue)); } else { foreach (var item in parentValue.EnumerateObject()) { var buffer = new StringBuilder(parentKey); buffer.Append('/'); buffer.Append(JsonPointer.Escape(item.Name)); _Flatten(buffer.ToString(), item.Value, result); } } break; } default: { result.AddProperty(parentKey, new JsonDocumentBuilder(parentValue)); break; } } }
public static bool TryGetValue(this JsonPointer pointer, JsonDocumentBuilder root, out JsonDocumentBuilder value) { value = root; foreach (var token in pointer) { if (!TryResolve(token, value, out value)) { return(false); } } return(true); }
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); } }
// unflatten static JsonDocumentBuilder SafeUnflatten(JsonDocumentBuilder value) { if (value.ValueKind != JsonValueKind.Object || value.GetObjectLength() == 0) { return value; } bool safe = true; int index = 0; foreach (var item in value.EnumerateObject()) { int n; if (!int.TryParse(item.Key, out n) || index++ != n) { safe = false; break; } } if (safe) { var j = new JsonDocumentBuilder(JsonValueKind.Array); foreach (var item in value.EnumerateObject()) { j.AddArrayItem(item.Value); } var a = new JsonDocumentBuilder(JsonValueKind.Array); foreach (var item in j.EnumerateArray()) { a.AddArrayItem(SafeUnflatten(item)); } return a; } else { var o = new JsonDocumentBuilder(JsonValueKind.Object); foreach (var item in value.EnumerateObject()) { //if (!o.ContainsPropertyName(item.Key)) //{ // o.AddProperty(item.Key, SafeUnflatten (item.Value)); //} o.TryAddProperty(item.Key, SafeUnflatten (item.Value)); } return o; } }
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"); } } } } }
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; }