// 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,
                                             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);
        }
        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;
        }