/// <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);
        }
Example #3
0
 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));
     }
 }
Example #4
0
        /// <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;
            }
        }
Example #15
0
        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);
        }
Example #16
0
        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);
        }
Example #17
0
        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;
        }