1225 lines
38 KiB
C#
1225 lines
38 KiB
C#
#region License
|
||
// Copyright (c) 2007 James Newton-King
|
||
//
|
||
// Permission is hereby granted, free of charge, to any person
|
||
// obtaining a copy of this software and associated documentation
|
||
// files (the "Software"), to deal in the Software without
|
||
// restriction, including without limitation the rights to use,
|
||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
// copies of the Software, and to permit persons to whom the
|
||
// Software is furnished to do so, subject to the following
|
||
// conditions:
|
||
//
|
||
// The above copyright notice and this permission notice shall be
|
||
// included in all copies or substantial portions of the Software.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||
// OTHER DEALINGS IN THE SOFTWARE.
|
||
#endregion
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
#if HAVE_INOTIFY_COLLECTION_CHANGED
|
||
using System.Collections.Specialized;
|
||
#endif
|
||
using System.Threading;
|
||
using LC.Newtonsoft.Json.Utilities;
|
||
using System.Collections;
|
||
using System.Globalization;
|
||
using System.ComponentModel;
|
||
using System.Diagnostics;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Diagnostics.CodeAnalysis;
|
||
#if !HAVE_LINQ
|
||
using LC.Newtonsoft.Json.Utilities.LinqBridge;
|
||
#else
|
||
using System.Linq;
|
||
#endif
|
||
|
||
namespace LC.Newtonsoft.Json.Linq
|
||
{
|
||
/// <summary>
|
||
/// Represents a token that can contain other tokens.
|
||
/// </summary>
|
||
public abstract partial class JContainer : JToken, IList<JToken>
|
||
#if HAVE_COMPONENT_MODEL
|
||
, ITypedList, IBindingList
|
||
#endif
|
||
, IList
|
||
#if HAVE_INOTIFY_COLLECTION_CHANGED
|
||
, INotifyCollectionChanged
|
||
#endif
|
||
{
|
||
#if HAVE_COMPONENT_MODEL
|
||
internal ListChangedEventHandler? _listChanged;
|
||
internal AddingNewEventHandler? _addingNew;
|
||
|
||
/// <summary>
|
||
/// Occurs when the list changes or an item in the list changes.
|
||
/// </summary>
|
||
public event ListChangedEventHandler ListChanged
|
||
{
|
||
add => _listChanged += value;
|
||
remove => _listChanged -= value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Occurs before an item is added to the collection.
|
||
/// </summary>
|
||
public event AddingNewEventHandler AddingNew
|
||
{
|
||
add => _addingNew += value;
|
||
remove => _addingNew -= value;
|
||
}
|
||
#endif
|
||
#if HAVE_INOTIFY_COLLECTION_CHANGED
|
||
internal NotifyCollectionChangedEventHandler? _collectionChanged;
|
||
|
||
/// <summary>
|
||
/// Occurs when the items list of the collection has changed, or the collection is reset.
|
||
/// </summary>
|
||
public event NotifyCollectionChangedEventHandler CollectionChanged
|
||
{
|
||
add { _collectionChanged += value; }
|
||
remove { _collectionChanged -= value; }
|
||
}
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// Gets the container's children tokens.
|
||
/// </summary>
|
||
/// <value>The container's children tokens.</value>
|
||
protected abstract IList<JToken> ChildrenTokens { get; }
|
||
|
||
private object? _syncRoot;
|
||
#if (HAVE_COMPONENT_MODEL || HAVE_INOTIFY_COLLECTION_CHANGED)
|
||
private bool _busy;
|
||
#endif
|
||
|
||
internal JContainer()
|
||
{
|
||
}
|
||
|
||
internal JContainer(JContainer other)
|
||
: this()
|
||
{
|
||
ValidationUtils.ArgumentNotNull(other, nameof(other));
|
||
|
||
int i = 0;
|
||
foreach (JToken child in other)
|
||
{
|
||
TryAddInternal(i, child, false);
|
||
i++;
|
||
}
|
||
|
||
CopyAnnotations(this, other);
|
||
}
|
||
|
||
internal void CheckReentrancy()
|
||
{
|
||
#if (HAVE_COMPONENT_MODEL || HAVE_INOTIFY_COLLECTION_CHANGED)
|
||
if (_busy)
|
||
{
|
||
throw new InvalidOperationException("Cannot change {0} during a collection change event.".FormatWith(CultureInfo.InvariantCulture, GetType()));
|
||
}
|
||
#endif
|
||
}
|
||
|
||
internal virtual IList<JToken> CreateChildrenCollection()
|
||
{
|
||
return new List<JToken>();
|
||
}
|
||
|
||
#if HAVE_COMPONENT_MODEL
|
||
/// <summary>
|
||
/// Raises the <see cref="AddingNew"/> event.
|
||
/// </summary>
|
||
/// <param name="e">The <see cref="AddingNewEventArgs"/> instance containing the event data.</param>
|
||
protected virtual void OnAddingNew(AddingNewEventArgs e)
|
||
{
|
||
_addingNew?.Invoke(this, e);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Raises the <see cref="ListChanged"/> event.
|
||
/// </summary>
|
||
/// <param name="e">The <see cref="ListChangedEventArgs"/> instance containing the event data.</param>
|
||
protected virtual void OnListChanged(ListChangedEventArgs e)
|
||
{
|
||
ListChangedEventHandler? handler = _listChanged;
|
||
|
||
if (handler != null)
|
||
{
|
||
_busy = true;
|
||
try
|
||
{
|
||
handler(this, e);
|
||
}
|
||
finally
|
||
{
|
||
_busy = false;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
#if HAVE_INOTIFY_COLLECTION_CHANGED
|
||
/// <summary>
|
||
/// Raises the <see cref="CollectionChanged"/> event.
|
||
/// </summary>
|
||
/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
|
||
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||
{
|
||
NotifyCollectionChangedEventHandler? handler = _collectionChanged;
|
||
|
||
if (handler != null)
|
||
{
|
||
_busy = true;
|
||
try
|
||
{
|
||
handler(this, e);
|
||
}
|
||
finally
|
||
{
|
||
_busy = false;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// Gets a value indicating whether this token has child tokens.
|
||
/// </summary>
|
||
/// <value>
|
||
/// <c>true</c> if this token has child values; otherwise, <c>false</c>.
|
||
/// </value>
|
||
public override bool HasValues => ChildrenTokens.Count > 0;
|
||
|
||
internal bool ContentsEqual(JContainer container)
|
||
{
|
||
if (container == this)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
IList<JToken> t1 = ChildrenTokens;
|
||
IList<JToken> t2 = container.ChildrenTokens;
|
||
|
||
if (t1.Count != t2.Count)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
for (int i = 0; i < t1.Count; i++)
|
||
{
|
||
if (!t1[i].DeepEquals(t2[i]))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the first child token of this token.
|
||
/// </summary>
|
||
/// <value>
|
||
/// A <see cref="JToken"/> containing the first child token of the <see cref="JToken"/>.
|
||
/// </value>
|
||
public override JToken? First
|
||
{
|
||
get
|
||
{
|
||
IList<JToken> children = ChildrenTokens;
|
||
return (children.Count > 0) ? children[0] : null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the last child token of this token.
|
||
/// </summary>
|
||
/// <value>
|
||
/// A <see cref="JToken"/> containing the last child token of the <see cref="JToken"/>.
|
||
/// </value>
|
||
public override JToken? Last
|
||
{
|
||
get
|
||
{
|
||
IList<JToken> children = ChildrenTokens;
|
||
int count = children.Count;
|
||
return (count > 0) ? children[count - 1] : null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns a collection of the child tokens of this token, in document order.
|
||
/// </summary>
|
||
/// <returns>
|
||
/// An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> containing the child tokens of this <see cref="JToken"/>, in document order.
|
||
/// </returns>
|
||
public override JEnumerable<JToken> Children()
|
||
{
|
||
return new JEnumerable<JToken>(ChildrenTokens);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns a collection of the child values of this token, in document order.
|
||
/// </summary>
|
||
/// <typeparam name="T">The type to convert the values to.</typeparam>
|
||
/// <returns>
|
||
/// A <see cref="IEnumerable{T}"/> containing the child values of this <see cref="JToken"/>, in document order.
|
||
/// </returns>
|
||
public override IEnumerable<T?> Values<T>() where T : default
|
||
{
|
||
return ChildrenTokens.Convert<JToken, T>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns a collection of the descendant tokens for this token in document order.
|
||
/// </summary>
|
||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> containing the descendant tokens of the <see cref="JToken"/>.</returns>
|
||
public IEnumerable<JToken> Descendants()
|
||
{
|
||
return GetDescendants(false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns a collection of the tokens that contain this token, and all descendant tokens of this token, in document order.
|
||
/// </summary>
|
||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> containing this token, and all the descendant tokens of the <see cref="JToken"/>.</returns>
|
||
public IEnumerable<JToken> DescendantsAndSelf()
|
||
{
|
||
return GetDescendants(true);
|
||
}
|
||
|
||
internal IEnumerable<JToken> GetDescendants(bool self)
|
||
{
|
||
if (self)
|
||
{
|
||
yield return this;
|
||
}
|
||
|
||
foreach (JToken o in ChildrenTokens)
|
||
{
|
||
yield return o;
|
||
if (o is JContainer c)
|
||
{
|
||
foreach (JToken d in c.Descendants())
|
||
{
|
||
yield return d;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
internal bool IsMultiContent([NotNullWhen(true)]object? content)
|
||
{
|
||
return (content is IEnumerable && !(content is string) && !(content is JToken) && !(content is byte[]));
|
||
}
|
||
|
||
internal JToken EnsureParentToken(JToken? item, bool skipParentCheck)
|
||
{
|
||
if (item == null)
|
||
{
|
||
return JValue.CreateNull();
|
||
}
|
||
|
||
if (skipParentCheck)
|
||
{
|
||
return item;
|
||
}
|
||
|
||
// to avoid a token having multiple parents or creating a recursive loop, create a copy if...
|
||
// the item already has a parent
|
||
// the item is being added to itself
|
||
// the item is being added to the root parent of itself
|
||
if (item.Parent != null || item == this || (item.HasValues && Root == item))
|
||
{
|
||
item = item.CloneToken();
|
||
}
|
||
|
||
return item;
|
||
}
|
||
|
||
internal abstract int IndexOfItem(JToken? item);
|
||
|
||
internal virtual bool InsertItem(int index, JToken? item, bool skipParentCheck)
|
||
{
|
||
IList<JToken> children = ChildrenTokens;
|
||
|
||
if (index > children.Count)
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(index), "Index must be within the bounds of the List.");
|
||
}
|
||
|
||
CheckReentrancy();
|
||
|
||
item = EnsureParentToken(item, skipParentCheck);
|
||
|
||
JToken? previous = (index == 0) ? null : children[index - 1];
|
||
// haven't inserted new token yet so next token is still at the inserting index
|
||
JToken? next = (index == children.Count) ? null : children[index];
|
||
|
||
ValidateToken(item, null);
|
||
|
||
item.Parent = this;
|
||
|
||
item.Previous = previous;
|
||
if (previous != null)
|
||
{
|
||
previous.Next = item;
|
||
}
|
||
|
||
item.Next = next;
|
||
if (next != null)
|
||
{
|
||
next.Previous = item;
|
||
}
|
||
|
||
children.Insert(index, item);
|
||
|
||
#if HAVE_COMPONENT_MODEL
|
||
if (_listChanged != null)
|
||
{
|
||
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, index));
|
||
}
|
||
#endif
|
||
#if HAVE_INOTIFY_COLLECTION_CHANGED
|
||
if (_collectionChanged != null)
|
||
{
|
||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
|
||
}
|
||
#endif
|
||
|
||
return true;
|
||
}
|
||
|
||
internal virtual void RemoveItemAt(int index)
|
||
{
|
||
IList<JToken> children = ChildrenTokens;
|
||
|
||
if (index < 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(index), "Index is less than 0.");
|
||
}
|
||
if (index >= children.Count)
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(index), "Index is equal to or greater than Count.");
|
||
}
|
||
|
||
CheckReentrancy();
|
||
|
||
JToken item = children[index];
|
||
JToken? previous = (index == 0) ? null : children[index - 1];
|
||
JToken? next = (index == children.Count - 1) ? null : children[index + 1];
|
||
|
||
if (previous != null)
|
||
{
|
||
previous.Next = next;
|
||
}
|
||
if (next != null)
|
||
{
|
||
next.Previous = previous;
|
||
}
|
||
|
||
item.Parent = null;
|
||
item.Previous = null;
|
||
item.Next = null;
|
||
|
||
children.RemoveAt(index);
|
||
|
||
#if HAVE_COMPONENT_MODEL
|
||
if (_listChanged != null)
|
||
{
|
||
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
|
||
}
|
||
#endif
|
||
#if HAVE_INOTIFY_COLLECTION_CHANGED
|
||
if (_collectionChanged != null)
|
||
{
|
||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
|
||
}
|
||
#endif
|
||
}
|
||
|
||
internal virtual bool RemoveItem(JToken? item)
|
||
{
|
||
if (item != null)
|
||
{
|
||
int index = IndexOfItem(item);
|
||
if (index >= 0)
|
||
{
|
||
RemoveItemAt(index);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
internal virtual JToken GetItem(int index)
|
||
{
|
||
return ChildrenTokens[index];
|
||
}
|
||
|
||
internal virtual void SetItem(int index, JToken? item)
|
||
{
|
||
IList<JToken> children = ChildrenTokens;
|
||
|
||
if (index < 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(index), "Index is less than 0.");
|
||
}
|
||
if (index >= children.Count)
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(index), "Index is equal to or greater than Count.");
|
||
}
|
||
|
||
JToken existing = children[index];
|
||
|
||
if (IsTokenUnchanged(existing, item))
|
||
{
|
||
return;
|
||
}
|
||
|
||
CheckReentrancy();
|
||
|
||
item = EnsureParentToken(item, false);
|
||
|
||
ValidateToken(item, existing);
|
||
|
||
JToken? previous = (index == 0) ? null : children[index - 1];
|
||
JToken? next = (index == children.Count - 1) ? null : children[index + 1];
|
||
|
||
item.Parent = this;
|
||
|
||
item.Previous = previous;
|
||
if (previous != null)
|
||
{
|
||
previous.Next = item;
|
||
}
|
||
|
||
item.Next = next;
|
||
if (next != null)
|
||
{
|
||
next.Previous = item;
|
||
}
|
||
|
||
children[index] = item;
|
||
|
||
existing.Parent = null;
|
||
existing.Previous = null;
|
||
existing.Next = null;
|
||
|
||
#if HAVE_COMPONENT_MODEL
|
||
if (_listChanged != null)
|
||
{
|
||
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
|
||
}
|
||
#endif
|
||
#if HAVE_INOTIFY_COLLECTION_CHANGED
|
||
if (_collectionChanged != null)
|
||
{
|
||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, existing, index));
|
||
}
|
||
#endif
|
||
}
|
||
|
||
internal virtual void ClearItems()
|
||
{
|
||
CheckReentrancy();
|
||
|
||
IList<JToken> children = ChildrenTokens;
|
||
|
||
foreach (JToken item in children)
|
||
{
|
||
item.Parent = null;
|
||
item.Previous = null;
|
||
item.Next = null;
|
||
}
|
||
|
||
children.Clear();
|
||
|
||
#if HAVE_COMPONENT_MODEL
|
||
if (_listChanged != null)
|
||
{
|
||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
||
}
|
||
#endif
|
||
#if HAVE_INOTIFY_COLLECTION_CHANGED
|
||
if (_collectionChanged != null)
|
||
{
|
||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||
}
|
||
#endif
|
||
}
|
||
|
||
internal virtual void ReplaceItem(JToken existing, JToken replacement)
|
||
{
|
||
if (existing == null || existing.Parent != this)
|
||
{
|
||
return;
|
||
}
|
||
|
||
int index = IndexOfItem(existing);
|
||
SetItem(index, replacement);
|
||
}
|
||
|
||
internal virtual bool ContainsItem(JToken? item)
|
||
{
|
||
return (IndexOfItem(item) != -1);
|
||
}
|
||
|
||
internal virtual void CopyItemsTo(Array array, int arrayIndex)
|
||
{
|
||
if (array == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(array));
|
||
}
|
||
if (arrayIndex < 0)
|
||
{
|
||
throw new ArgumentOutOfRangeException(nameof(arrayIndex), "arrayIndex is less than 0.");
|
||
}
|
||
if (arrayIndex >= array.Length && arrayIndex != 0)
|
||
{
|
||
throw new ArgumentException("arrayIndex is equal to or greater than the length of array.");
|
||
}
|
||
if (Count > array.Length - arrayIndex)
|
||
{
|
||
throw new ArgumentException("The number of elements in the source JObject is greater than the available space from arrayIndex to the end of the destination array.");
|
||
}
|
||
|
||
int index = 0;
|
||
foreach (JToken token in ChildrenTokens)
|
||
{
|
||
array.SetValue(token, arrayIndex + index);
|
||
index++;
|
||
}
|
||
}
|
||
|
||
internal static bool IsTokenUnchanged(JToken currentValue, JToken? newValue)
|
||
{
|
||
if (currentValue is JValue v1)
|
||
{
|
||
if (newValue == null)
|
||
{
|
||
// null will get turned into a JValue of type null
|
||
return v1.Type == JTokenType.Null;
|
||
}
|
||
|
||
return v1.Equals(newValue);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
internal virtual void ValidateToken(JToken o, JToken? existing)
|
||
{
|
||
ValidationUtils.ArgumentNotNull(o, nameof(o));
|
||
|
||
if (o.Type == JTokenType.Property)
|
||
{
|
||
throw new ArgumentException("Can not add {0} to {1}.".FormatWith(CultureInfo.InvariantCulture, o.GetType(), GetType()));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds the specified content as children of this <see cref="JToken"/>.
|
||
/// </summary>
|
||
/// <param name="content">The content to be added.</param>
|
||
public virtual void Add(object? content)
|
||
{
|
||
TryAddInternal(ChildrenTokens.Count, content, false);
|
||
}
|
||
|
||
internal bool TryAdd(object? content)
|
||
{
|
||
return TryAddInternal(ChildrenTokens.Count, content, false);
|
||
}
|
||
|
||
internal void AddAndSkipParentCheck(JToken token)
|
||
{
|
||
TryAddInternal(ChildrenTokens.Count, token, true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds the specified content as the first children of this <see cref="JToken"/>.
|
||
/// </summary>
|
||
/// <param name="content">The content to be added.</param>
|
||
public void AddFirst(object? content)
|
||
{
|
||
TryAddInternal(0, content, false);
|
||
}
|
||
|
||
internal bool TryAddInternal(int index, object? content, bool skipParentCheck)
|
||
{
|
||
if (IsMultiContent(content))
|
||
{
|
||
IEnumerable enumerable = (IEnumerable)content;
|
||
|
||
int multiIndex = index;
|
||
foreach (object c in enumerable)
|
||
{
|
||
TryAddInternal(multiIndex, c, skipParentCheck);
|
||
multiIndex++;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
JToken item = CreateFromContent(content);
|
||
|
||
return InsertItem(index, item, skipParentCheck);
|
||
}
|
||
}
|
||
|
||
internal static JToken CreateFromContent(object? content)
|
||
{
|
||
if (content is JToken token)
|
||
{
|
||
return token;
|
||
}
|
||
|
||
return new JValue(content);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a <see cref="JsonWriter"/> that can be used to add tokens to the <see cref="JToken"/>.
|
||
/// </summary>
|
||
/// <returns>A <see cref="JsonWriter"/> that is ready to have content written to it.</returns>
|
||
public JsonWriter CreateWriter()
|
||
{
|
||
return new JTokenWriter(this);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Replaces the child nodes of this token with the specified content.
|
||
/// </summary>
|
||
/// <param name="content">The content.</param>
|
||
public void ReplaceAll(object content)
|
||
{
|
||
ClearItems();
|
||
Add(content);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Removes the child nodes from this token.
|
||
/// </summary>
|
||
public void RemoveAll()
|
||
{
|
||
ClearItems();
|
||
}
|
||
|
||
internal abstract void MergeItem(object content, JsonMergeSettings? settings);
|
||
|
||
/// <summary>
|
||
/// Merge the specified content into this <see cref="JToken"/>.
|
||
/// </summary>
|
||
/// <param name="content">The content to be merged.</param>
|
||
public void Merge(object content)
|
||
{
|
||
MergeItem(content, null);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Merge the specified content into this <see cref="JToken"/> using <see cref="JsonMergeSettings"/>.
|
||
/// </summary>
|
||
/// <param name="content">The content to be merged.</param>
|
||
/// <param name="settings">The <see cref="JsonMergeSettings"/> used to merge the content.</param>
|
||
public void Merge(object content, JsonMergeSettings? settings)
|
||
{
|
||
MergeItem(content, settings);
|
||
}
|
||
|
||
internal void ReadTokenFrom(JsonReader reader, JsonLoadSettings? options)
|
||
{
|
||
int startDepth = reader.Depth;
|
||
|
||
if (!reader.Read())
|
||
{
|
||
throw JsonReaderException.Create(reader, "Error reading {0} from JsonReader.".FormatWith(CultureInfo.InvariantCulture, GetType().Name));
|
||
}
|
||
|
||
ReadContentFrom(reader, options);
|
||
|
||
int endDepth = reader.Depth;
|
||
|
||
if (endDepth > startDepth)
|
||
{
|
||
throw JsonReaderException.Create(reader, "Unexpected end of content while loading {0}.".FormatWith(CultureInfo.InvariantCulture, GetType().Name));
|
||
}
|
||
}
|
||
|
||
internal void ReadContentFrom(JsonReader r, JsonLoadSettings? settings)
|
||
{
|
||
ValidationUtils.ArgumentNotNull(r, nameof(r));
|
||
IJsonLineInfo? lineInfo = r as IJsonLineInfo;
|
||
|
||
JContainer? parent = this;
|
||
|
||
do
|
||
{
|
||
if (parent is JProperty p && p.Value != null)
|
||
{
|
||
if (parent == this)
|
||
{
|
||
return;
|
||
}
|
||
|
||
parent = parent.Parent;
|
||
}
|
||
|
||
MiscellaneousUtils.Assert(parent != null);
|
||
|
||
switch (r.TokenType)
|
||
{
|
||
case JsonToken.None:
|
||
// new reader. move to actual content
|
||
break;
|
||
case JsonToken.StartArray:
|
||
JArray a = new JArray();
|
||
a.SetLineInfo(lineInfo, settings);
|
||
parent.Add(a);
|
||
parent = a;
|
||
break;
|
||
|
||
case JsonToken.EndArray:
|
||
if (parent == this)
|
||
{
|
||
return;
|
||
}
|
||
|
||
parent = parent.Parent;
|
||
break;
|
||
case JsonToken.StartObject:
|
||
JObject o = new JObject();
|
||
o.SetLineInfo(lineInfo, settings);
|
||
parent.Add(o);
|
||
parent = o;
|
||
break;
|
||
case JsonToken.EndObject:
|
||
if (parent == this)
|
||
{
|
||
return;
|
||
}
|
||
|
||
parent = parent.Parent;
|
||
break;
|
||
case JsonToken.StartConstructor:
|
||
JConstructor constructor = new JConstructor(r.Value!.ToString());
|
||
constructor.SetLineInfo(lineInfo, settings);
|
||
parent.Add(constructor);
|
||
parent = constructor;
|
||
break;
|
||
case JsonToken.EndConstructor:
|
||
if (parent == this)
|
||
{
|
||
return;
|
||
}
|
||
|
||
parent = parent.Parent;
|
||
break;
|
||
case JsonToken.String:
|
||
case JsonToken.Integer:
|
||
case JsonToken.Float:
|
||
case JsonToken.Date:
|
||
case JsonToken.Boolean:
|
||
case JsonToken.Bytes:
|
||
JValue v = new JValue(r.Value);
|
||
v.SetLineInfo(lineInfo, settings);
|
||
parent.Add(v);
|
||
break;
|
||
case JsonToken.Comment:
|
||
if (settings != null && settings.CommentHandling == CommentHandling.Load)
|
||
{
|
||
v = JValue.CreateComment(r.Value!.ToString());
|
||
v.SetLineInfo(lineInfo, settings);
|
||
parent.Add(v);
|
||
}
|
||
break;
|
||
case JsonToken.Null:
|
||
v = JValue.CreateNull();
|
||
v.SetLineInfo(lineInfo, settings);
|
||
parent.Add(v);
|
||
break;
|
||
case JsonToken.Undefined:
|
||
v = JValue.CreateUndefined();
|
||
v.SetLineInfo(lineInfo, settings);
|
||
parent.Add(v);
|
||
break;
|
||
case JsonToken.PropertyName:
|
||
JProperty? property = ReadProperty(r, settings, lineInfo, parent);
|
||
if (property != null)
|
||
{
|
||
parent = property;
|
||
}
|
||
else
|
||
{
|
||
r.Skip();
|
||
}
|
||
break;
|
||
default:
|
||
throw new InvalidOperationException("The JsonReader should not be on a token of type {0}.".FormatWith(CultureInfo.InvariantCulture, r.TokenType));
|
||
}
|
||
} while (r.Read());
|
||
}
|
||
|
||
private static JProperty? ReadProperty(JsonReader r, JsonLoadSettings? settings, IJsonLineInfo? lineInfo, JContainer parent)
|
||
{
|
||
DuplicatePropertyNameHandling duplicatePropertyNameHandling = settings?.DuplicatePropertyNameHandling ?? DuplicatePropertyNameHandling.Replace;
|
||
|
||
JObject parentObject = (JObject)parent;
|
||
string propertyName = r.Value!.ToString();
|
||
JProperty? existingPropertyWithName = parentObject.Property(propertyName, StringComparison.Ordinal);
|
||
if (existingPropertyWithName != null)
|
||
{
|
||
if (duplicatePropertyNameHandling == DuplicatePropertyNameHandling.Ignore)
|
||
{
|
||
return null;
|
||
}
|
||
else if (duplicatePropertyNameHandling == DuplicatePropertyNameHandling.Error)
|
||
{
|
||
throw JsonReaderException.Create(r, "Property with the name '{0}' already exists in the current JSON object.".FormatWith(CultureInfo.InvariantCulture, propertyName));
|
||
}
|
||
}
|
||
|
||
JProperty property = new JProperty(propertyName);
|
||
property.SetLineInfo(lineInfo, settings);
|
||
// handle multiple properties with the same name in JSON
|
||
if (existingPropertyWithName == null)
|
||
{
|
||
parent.Add(property);
|
||
}
|
||
else
|
||
{
|
||
existingPropertyWithName.Replace(property);
|
||
}
|
||
|
||
return property;
|
||
}
|
||
|
||
internal int ContentsHashCode()
|
||
{
|
||
int hashCode = 0;
|
||
foreach (JToken item in ChildrenTokens)
|
||
{
|
||
hashCode ^= item.GetDeepHashCode();
|
||
}
|
||
return hashCode;
|
||
}
|
||
|
||
#if HAVE_COMPONENT_MODEL
|
||
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
|
||
{
|
||
return string.Empty;
|
||
}
|
||
|
||
PropertyDescriptorCollection? ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
|
||
{
|
||
ICustomTypeDescriptor? d = First as ICustomTypeDescriptor;
|
||
return d?.GetProperties();
|
||
}
|
||
#endif
|
||
|
||
#region IList<JToken> Members
|
||
int IList<JToken>.IndexOf(JToken item)
|
||
{
|
||
return IndexOfItem(item);
|
||
}
|
||
|
||
void IList<JToken>.Insert(int index, JToken item)
|
||
{
|
||
InsertItem(index, item, false);
|
||
}
|
||
|
||
void IList<JToken>.RemoveAt(int index)
|
||
{
|
||
RemoveItemAt(index);
|
||
}
|
||
|
||
JToken IList<JToken>.this[int index]
|
||
{
|
||
get => GetItem(index);
|
||
set => SetItem(index, value);
|
||
}
|
||
#endregion
|
||
|
||
#region ICollection<JToken> Members
|
||
void ICollection<JToken>.Add(JToken item)
|
||
{
|
||
Add(item);
|
||
}
|
||
|
||
void ICollection<JToken>.Clear()
|
||
{
|
||
ClearItems();
|
||
}
|
||
|
||
bool ICollection<JToken>.Contains(JToken item)
|
||
{
|
||
return ContainsItem(item);
|
||
}
|
||
|
||
void ICollection<JToken>.CopyTo(JToken[] array, int arrayIndex)
|
||
{
|
||
CopyItemsTo(array, arrayIndex);
|
||
}
|
||
|
||
bool ICollection<JToken>.IsReadOnly => false;
|
||
|
||
bool ICollection<JToken>.Remove(JToken item)
|
||
{
|
||
return RemoveItem(item);
|
||
}
|
||
#endregion
|
||
|
||
private JToken? EnsureValue(object value)
|
||
{
|
||
if (value == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (value is JToken token)
|
||
{
|
||
return token;
|
||
}
|
||
|
||
throw new ArgumentException("Argument is not a JToken.");
|
||
}
|
||
|
||
#region IList Members
|
||
int IList.Add(object value)
|
||
{
|
||
Add(EnsureValue(value));
|
||
return Count - 1;
|
||
}
|
||
|
||
void IList.Clear()
|
||
{
|
||
ClearItems();
|
||
}
|
||
|
||
bool IList.Contains(object value)
|
||
{
|
||
return ContainsItem(EnsureValue(value));
|
||
}
|
||
|
||
int IList.IndexOf(object value)
|
||
{
|
||
return IndexOfItem(EnsureValue(value));
|
||
}
|
||
|
||
void IList.Insert(int index, object value)
|
||
{
|
||
InsertItem(index, EnsureValue(value), false);
|
||
}
|
||
|
||
bool IList.IsFixedSize => false;
|
||
|
||
bool IList.IsReadOnly => false;
|
||
|
||
void IList.Remove(object value)
|
||
{
|
||
RemoveItem(EnsureValue(value));
|
||
}
|
||
|
||
void IList.RemoveAt(int index)
|
||
{
|
||
RemoveItemAt(index);
|
||
}
|
||
|
||
object IList.this[int index]
|
||
{
|
||
get => GetItem(index);
|
||
set => SetItem(index, EnsureValue(value));
|
||
}
|
||
#endregion
|
||
|
||
#region ICollection Members
|
||
void ICollection.CopyTo(Array array, int index)
|
||
{
|
||
CopyItemsTo(array, index);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the count of child JSON tokens.
|
||
/// </summary>
|
||
/// <value>The count of child JSON tokens.</value>
|
||
public int Count => ChildrenTokens.Count;
|
||
|
||
bool ICollection.IsSynchronized => false;
|
||
|
||
object ICollection.SyncRoot
|
||
{
|
||
get
|
||
{
|
||
if (_syncRoot == null)
|
||
{
|
||
Interlocked.CompareExchange(ref _syncRoot, new object(), null);
|
||
}
|
||
|
||
return _syncRoot;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region IBindingList Members
|
||
#if HAVE_COMPONENT_MODEL
|
||
void IBindingList.AddIndex(PropertyDescriptor property)
|
||
{
|
||
}
|
||
|
||
object IBindingList.AddNew()
|
||
{
|
||
AddingNewEventArgs args = new AddingNewEventArgs();
|
||
OnAddingNew(args);
|
||
|
||
if (args.NewObject == null)
|
||
{
|
||
throw new JsonException("Could not determine new value to add to '{0}'.".FormatWith(CultureInfo.InvariantCulture, GetType()));
|
||
}
|
||
|
||
if (!(args.NewObject is JToken newItem))
|
||
{
|
||
throw new JsonException("New item to be added to collection must be compatible with {0}.".FormatWith(CultureInfo.InvariantCulture, typeof(JToken)));
|
||
}
|
||
|
||
Add(newItem);
|
||
|
||
return newItem;
|
||
}
|
||
|
||
bool IBindingList.AllowEdit => true;
|
||
|
||
bool IBindingList.AllowNew => true;
|
||
|
||
bool IBindingList.AllowRemove => true;
|
||
|
||
void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)
|
||
{
|
||
throw new NotSupportedException();
|
||
}
|
||
|
||
int IBindingList.Find(PropertyDescriptor property, object key)
|
||
{
|
||
throw new NotSupportedException();
|
||
}
|
||
|
||
bool IBindingList.IsSorted => false;
|
||
|
||
void IBindingList.RemoveIndex(PropertyDescriptor property)
|
||
{
|
||
}
|
||
|
||
void IBindingList.RemoveSort()
|
||
{
|
||
throw new NotSupportedException();
|
||
}
|
||
|
||
ListSortDirection IBindingList.SortDirection => ListSortDirection.Ascending;
|
||
|
||
PropertyDescriptor? IBindingList.SortProperty => null;
|
||
|
||
bool IBindingList.SupportsChangeNotification => true;
|
||
|
||
bool IBindingList.SupportsSearching => false;
|
||
|
||
bool IBindingList.SupportsSorting => false;
|
||
#endif
|
||
#endregion
|
||
|
||
internal static void MergeEnumerableContent(JContainer target, IEnumerable content, JsonMergeSettings? settings)
|
||
{
|
||
switch (settings?.MergeArrayHandling ?? MergeArrayHandling.Concat)
|
||
{
|
||
case MergeArrayHandling.Concat:
|
||
foreach (JToken item in content)
|
||
{
|
||
target.Add(item);
|
||
}
|
||
break;
|
||
case MergeArrayHandling.Union:
|
||
#if HAVE_HASH_SET
|
||
HashSet<JToken> items = new HashSet<JToken>(target, EqualityComparer);
|
||
|
||
foreach (JToken item in content)
|
||
{
|
||
if (items.Add(item))
|
||
{
|
||
target.Add(item);
|
||
}
|
||
}
|
||
#else
|
||
Dictionary<JToken, bool> items = new Dictionary<JToken, bool>(EqualityComparer);
|
||
foreach (JToken t in target)
|
||
{
|
||
items[t] = true;
|
||
}
|
||
|
||
foreach (JToken item in content)
|
||
{
|
||
if (!items.ContainsKey(item))
|
||
{
|
||
items[item] = true;
|
||
target.Add(item);
|
||
}
|
||
}
|
||
#endif
|
||
break;
|
||
case MergeArrayHandling.Replace:
|
||
if (target == content)
|
||
{
|
||
break;
|
||
}
|
||
target.ClearItems();
|
||
foreach (JToken item in content)
|
||
{
|
||
target.Add(item);
|
||
}
|
||
break;
|
||
case MergeArrayHandling.Merge:
|
||
int i = 0;
|
||
foreach (object targetItem in content)
|
||
{
|
||
if (i < target.Count)
|
||
{
|
||
JToken? sourceItem = target[i];
|
||
|
||
if (sourceItem is JContainer existingContainer)
|
||
{
|
||
existingContainer.Merge(targetItem, settings);
|
||
}
|
||
else
|
||
{
|
||
if (targetItem != null)
|
||
{
|
||
JToken contentValue = CreateFromContent(targetItem);
|
||
if (contentValue.Type != JTokenType.Null)
|
||
{
|
||
target[i] = contentValue;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
target.Add(targetItem);
|
||
}
|
||
|
||
i++;
|
||
}
|
||
break;
|
||
default:
|
||
throw new ArgumentOutOfRangeException(nameof(settings), "Unexpected merge array handling when merging JSON.");
|
||
}
|
||
}
|
||
}
|
||
} |