Add JsonCloneSettings to disable copy annotations (#2757)

Co-authored-by: Sneha Shivakumar <snshivakumar@microsoft.com>
This commit is contained in:
James Newton-King
2022-11-14 08:55:47 +08:00
committed by GitHub
co-authored by Sneha Shivakumar
parent d0a328e8a4
commit b13717a1c1
13 changed files with 265 additions and 49 deletions
@@ -303,11 +303,140 @@ namespace Newtonsoft.Json.Tests.Linq
Assert.AreEqual(0, o2.Annotations<Version>().Count());
}
[Test]
public void NestedAnnotationsAreCopied()
{
Version version = new Version(1, 2, 3, 4);
JObject o = new JObject();
o.AddAnnotation("string!");
o.AddAnnotation(version);
JValue v = new JValue(true);
v.AddAnnotation("string!");
v.AddAnnotation(version);
o["Item1"] = v;
JObject o2 = (JObject)o.DeepClone();
Assert.AreEqual("string!", o2.Annotation<string>());
Assert.AreEqual(version, o2.Annotation<Version>());
JValue v2 = (JValue)o2["Item1"];
Assert.AreEqual("string!", v2.Annotation<string>());
Assert.AreEqual(version, v2.Annotation<Version>());
}
[Test]
public void NestedAnnotationsAreCopiedWithDefault()
{
Version version = new Version(1, 2, 3, 4);
JsonCloneSettings settings = new JsonCloneSettings();
JObject o = new JObject();
o.AddAnnotation("string!");
o.AddAnnotation(version);
JValue v = new JValue(true);
v.AddAnnotation("string!");
v.AddAnnotation(version);
o["Item1"] = v;
JObject o2 = (JObject)o.DeepClone(settings);
Assert.AreEqual("string!", o2.Annotation<string>());
Assert.AreEqual(version, o2.Annotation<Version>());
JValue v2 = (JValue)o2["Item1"];
Assert.AreEqual("string!", v2.Annotation<string>());
Assert.AreEqual(version, v2.Annotation<Version>());
}
[Test]
public void NestedAnnotationsAreNotCopiedWithSettingsCopyAnnotationsFalse()
{
Version version = new Version(1, 2, 3, 4);
JsonCloneSettings settings = new JsonCloneSettings() { CopyAnnotations = false };
JObject o = new JObject();
o.AddAnnotation("string!");
o.AddAnnotation(version);
JValue v = new JValue(true);
v.AddAnnotation("string!");
v.AddAnnotation(version);
o["Item1"] = v;
JObject o2 = (JObject)o.DeepClone(settings);
Assert.IsNull(o2.Annotation<string>());
Assert.AreEqual(0, o2.Annotations<Version>().Count());
JValue v2 = (JValue)o2["Item1"];
Assert.IsNull(v2.Annotation<string>());
Assert.AreEqual(0, v2.Annotations<Version>().Count());
}
private void AssertCloneCopy<T>(JToken t, T annotation) where T : class
{
Assert.AreEqual(annotation, t.DeepClone().Annotation<T>());
}
[Test]
public void MultipleAnnotationsAreNotCopiedWithSetting()
{
Version version = new Version(1, 2, 3, 4);
JsonCloneSettings settings = new JsonCloneSettings() { CopyAnnotations = false };
JObject o = new JObject();
o.AddAnnotation("string!");
o.AddAnnotation(version);
JObject o2 = (JObject)o.DeepClone(settings);
Assert.IsNull(o2.Annotation<string>());
Assert.AreEqual(0, o2.Annotations<Version>().Count());
JArray a = new JArray();
a.AddAnnotation("string!");
a.AddAnnotation(version);
JArray a2 = (JArray)a.DeepClone(settings);
Assert.IsNull(a2.Annotation<string>());
Assert.AreEqual(0, a2.Annotations<Version>().Count());
JProperty p = new JProperty("test");
p.AddAnnotation("string!");
p.AddAnnotation(version);
JProperty p2 = (JProperty)p.DeepClone(settings);
Assert.IsNull(p2.Annotation<string>());
Assert.AreEqual(0, p2.Annotations<Version>().Count());
JRaw r = new JRaw("test");
r.AddAnnotation("string!");
r.AddAnnotation(version);
JRaw r2 = (JRaw)r.DeepClone(settings);
Assert.IsNull(r2.Annotation<string>());
Assert.AreEqual(0, r2.Annotations<Version>().Count());
JConstructor c = new JConstructor("test");
c.AddAnnotation("string!");
c.AddAnnotation(version);
JConstructor c2 = (JConstructor)c.DeepClone(settings);
Assert.IsNull(c2.Annotation<string>());
Assert.AreEqual(0, c2.Annotations<Version>().Count());
JValue v = new JValue("test");
v.AddAnnotation("string!");
v.AddAnnotation(version);
JValue v2 = (JValue)v.DeepClone(settings);
Assert.IsNull(v2.Annotation<string>());
Assert.AreEqual(0, v2.Annotations<Version>().Count());
}
#if !NET20
[Test]
public void Example()
+1 -1
View File
@@ -52,7 +52,7 @@ namespace Newtonsoft.Json.Tests.Linq
public void RawClone()
{
JRaw r1 = new JRaw("raw1");
JToken r2 = r1.CloneToken();
JToken r2 = r1.DeepClone();
CustomAssert.IsInstanceOfType(typeof(JRaw), r2);
}
@@ -324,7 +324,7 @@ namespace Newtonsoft.Json.Tests.Serialization
]";
JToken t1 = JToken.Parse(json);
JToken t2 = t1.CloneToken();
JToken t2 = t1.DeepClone();
List<EmployeeReference> employees = t1.ToObject<List<EmployeeReference>>(JsonSerializer.Create(new JsonSerializerSettings
{
+9 -4
View File
@@ -66,7 +66,12 @@ namespace Newtonsoft.Json.Linq
/// </summary>
/// <param name="other">A <see cref="JArray"/> object to copy from.</param>
public JArray(JArray other)
: base(other)
: base(other, settings: null)
{
}
internal JArray(JArray other, JsonCloneSettings? settings)
: base(other, settings)
{
}
@@ -93,9 +98,9 @@ namespace Newtonsoft.Json.Linq
return (node is JArray t && ContentsEqual(t));
}
internal override JToken CloneToken()
internal override JToken CloneToken(JsonCloneSettings? settings = null)
{
return new JArray(this);
return new JArray(this, settings);
}
/// <summary>
@@ -309,7 +314,7 @@ namespace Newtonsoft.Json.Linq
/// </exception>
public void Insert(int index, JToken item)
{
InsertItem(index, item, false);
InsertItem(index, item, false, copyAnnotations: true);
}
/// <summary>
+9 -3
View File
@@ -97,7 +97,13 @@ namespace Newtonsoft.Json.Linq
/// </summary>
/// <param name="other">A <see cref="JConstructor"/> object to copy from.</param>
public JConstructor(JConstructor other)
: base(other)
: base(other, settings: null)
{
_name = other.Name;
}
internal JConstructor(JConstructor other, JsonCloneSettings? settings)
: base(other, settings)
{
_name = other.Name;
}
@@ -147,9 +153,9 @@ namespace Newtonsoft.Json.Linq
return (node is JConstructor c && _name == c.Name && ContentsEqual(c));
}
internal override JToken CloneToken()
internal override JToken CloneToken(JsonCloneSettings? settings = null)
{
return new JConstructor(this);
return new JConstructor(this, settings);
}
/// <summary>
+28 -18
View File
@@ -106,19 +106,24 @@ namespace Newtonsoft.Json.Linq
{
}
internal JContainer(JContainer other)
internal JContainer(JContainer other, JsonCloneSettings? settings)
: this()
{
ValidationUtils.ArgumentNotNull(other, nameof(other));
bool copyAnnotations = settings?.CopyAnnotations ?? true;
if (copyAnnotations)
{
CopyAnnotations(this, other);
}
int i = 0;
foreach (JToken child in other)
{
TryAddInternal(i, child, false);
TryAddInternal(i, child, false, copyAnnotations);
i++;
}
CopyAnnotations(this, other);
}
internal void CheckReentrancy()
@@ -323,7 +328,7 @@ namespace Newtonsoft.Json.Linq
return (content is IEnumerable && !(content is string) && !(content is JToken) && !(content is byte[]));
}
internal JToken EnsureParentToken(JToken? item, bool skipParentCheck)
internal JToken EnsureParentToken(JToken? item, bool skipParentCheck, bool copyAnnotations)
{
if (item == null)
{
@@ -341,7 +346,12 @@ namespace Newtonsoft.Json.Linq
// the item is being added to the root parent of itself
if (item.Parent != null || item == this || (item.HasValues && Root == item))
{
item = item.CloneToken();
// Avoid allocating settings when copy annotations is false.
JsonCloneSettings? settings = copyAnnotations
? null
: JsonCloneSettings.SkipCopyAnnotations;
item = item.CloneToken(settings);
}
return item;
@@ -349,7 +359,7 @@ namespace Newtonsoft.Json.Linq
internal abstract int IndexOfItem(JToken? item);
internal virtual bool InsertItem(int index, JToken? item, bool skipParentCheck)
internal virtual bool InsertItem(int index, JToken? item, bool skipParentCheck, bool copyAnnotations)
{
IList<JToken> children = ChildrenTokens;
@@ -360,7 +370,7 @@ namespace Newtonsoft.Json.Linq
CheckReentrancy();
item = EnsureParentToken(item, skipParentCheck);
item = EnsureParentToken(item, skipParentCheck, copyAnnotations);
JToken? previous = (index == 0) ? null : children[index - 1];
// haven't inserted new token yet so next token is still at the inserting index
@@ -490,7 +500,7 @@ namespace Newtonsoft.Json.Linq
CheckReentrancy();
item = EnsureParentToken(item, false);
item = EnsureParentToken(item, false, copyAnnotations: true);
ValidateToken(item, existing);
@@ -635,17 +645,17 @@ namespace Newtonsoft.Json.Linq
/// <param name="content">The content to be added.</param>
public virtual void Add(object? content)
{
TryAddInternal(ChildrenTokens.Count, content, false);
TryAddInternal(ChildrenTokens.Count, content, false, copyAnnotations: true);
}
internal bool TryAdd(object? content)
{
return TryAddInternal(ChildrenTokens.Count, content, false);
return TryAddInternal(ChildrenTokens.Count, content, false, copyAnnotations: true);
}
internal void AddAndSkipParentCheck(JToken token)
{
TryAddInternal(ChildrenTokens.Count, token, true);
TryAddInternal(ChildrenTokens.Count, token, true, copyAnnotations: true);
}
/// <summary>
@@ -654,10 +664,10 @@ namespace Newtonsoft.Json.Linq
/// <param name="content">The content to be added.</param>
public void AddFirst(object? content)
{
TryAddInternal(0, content, false);
TryAddInternal(0, content, false, copyAnnotations: true);
}
internal bool TryAddInternal(int index, object? content, bool skipParentCheck)
internal bool TryAddInternal(int index, object? content, bool skipParentCheck, bool copyAnnotations)
{
if (IsMultiContent(content))
{
@@ -666,7 +676,7 @@ namespace Newtonsoft.Json.Linq
int multiIndex = index;
foreach (object c in enumerable)
{
TryAddInternal(multiIndex, c, skipParentCheck);
TryAddInternal(multiIndex, c, skipParentCheck, copyAnnotations);
multiIndex++;
}
@@ -676,7 +686,7 @@ namespace Newtonsoft.Json.Linq
{
JToken item = CreateFromContent(content);
return InsertItem(index, item, skipParentCheck);
return InsertItem(index, item, skipParentCheck, copyAnnotations);
}
}
@@ -963,7 +973,7 @@ namespace Newtonsoft.Json.Linq
void IList<JToken>.Insert(int index, JToken item)
{
InsertItem(index, item, false);
InsertItem(index, item, false, copyAnnotations: true);
}
void IList<JToken>.RemoveAt(int index)
@@ -1046,7 +1056,7 @@ namespace Newtonsoft.Json.Linq
void IList.Insert(int index, object? value)
{
InsertItem(index, EnsureValue(value), false);
InsertItem(index, EnsureValue(value), false, copyAnnotations: false);
}
bool IList.IsFixedSize => false;
+10 -5
View File
@@ -93,7 +93,12 @@ namespace Newtonsoft.Json.Linq
/// </summary>
/// <param name="other">A <see cref="JObject"/> object to copy from.</param>
public JObject(JObject other)
: base(other)
: base(other, settings: null)
{
}
internal JObject(JObject other, JsonCloneSettings? settings)
: base(other, settings)
{
}
@@ -135,7 +140,7 @@ namespace Newtonsoft.Json.Linq
return _properties.IndexOfReference(item);
}
internal override bool InsertItem(int index, JToken? item, bool skipParentCheck)
internal override bool InsertItem(int index, JToken? item, bool skipParentCheck, bool copyAnnotations)
{
// don't add comments to JObject, no name to reference comment by
if (item != null && item.Type == JTokenType.Comment)
@@ -143,7 +148,7 @@ namespace Newtonsoft.Json.Linq
return false;
}
return base.InsertItem(index, item, skipParentCheck);
return base.InsertItem(index, item, skipParentCheck, copyAnnotations);
}
internal override void ValidateToken(JToken o, JToken? existing)
@@ -244,9 +249,9 @@ namespace Newtonsoft.Json.Linq
#endif
}
internal override JToken CloneToken()
internal override JToken CloneToken(JsonCloneSettings? settings)
{
return new JObject(this);
return new JObject(this, settings);
}
/// <summary>
+12 -6
View File
@@ -173,7 +173,7 @@ namespace Newtonsoft.Json.Linq
if (_content._token == null)
{
InsertItem(0, newValue, false);
InsertItem(0, newValue, false, copyAnnotations: true);
}
else
{
@@ -187,7 +187,13 @@ namespace Newtonsoft.Json.Linq
/// </summary>
/// <param name="other">A <see cref="JProperty"/> object to copy from.</param>
public JProperty(JProperty other)
: base(other)
: base(other, settings: null)
{
_name = other.Name;
}
internal JProperty(JProperty other, JsonCloneSettings? settings)
: base(other, settings)
{
_name = other.Name;
}
@@ -241,7 +247,7 @@ namespace Newtonsoft.Json.Linq
return _content.IndexOf(item);
}
internal override bool InsertItem(int index, JToken? item, bool skipParentCheck)
internal override bool InsertItem(int index, JToken? item, bool skipParentCheck, bool copyAnnotations)
{
// don't add comments to JProperty
if (item != null && item.Type == JTokenType.Comment)
@@ -254,7 +260,7 @@ namespace Newtonsoft.Json.Linq
throw new JsonException("{0} cannot have multiple values.".FormatWith(CultureInfo.InvariantCulture, typeof(JProperty)));
}
return base.InsertItem(0, item, false);
return base.InsertItem(0, item, false, copyAnnotations);
}
internal override bool ContainsItem(JToken? item)
@@ -282,9 +288,9 @@ namespace Newtonsoft.Json.Linq
return (node is JProperty t && _name == t.Name && ContentsEqual(t));
}
internal override JToken CloneToken()
internal override JToken CloneToken(JsonCloneSettings? settings)
{
return new JProperty(this);
return new JProperty(this, settings);
}
/// <summary>
+8 -3
View File
@@ -38,7 +38,12 @@ namespace Newtonsoft.Json.Linq
/// </summary>
/// <param name="other">A <see cref="JRaw"/> object to copy from.</param>
public JRaw(JRaw other)
: base(other)
: base(other, settings: null)
{
}
internal JRaw(JRaw other, JsonCloneSettings? settings)
: base(other, settings)
{
}
@@ -67,9 +72,9 @@ namespace Newtonsoft.Json.Linq
}
}
internal override JToken CloneToken()
internal override JToken CloneToken(JsonCloneSettings? settings)
{
return new JRaw(this);
return new JRaw(this, settings);
}
}
}
+14 -4
View File
@@ -131,7 +131,7 @@ namespace Newtonsoft.Json.Linq
}
}
internal abstract JToken CloneToken();
internal abstract JToken CloneToken(JsonCloneSettings? settings);
internal abstract bool DeepEquals(JToken node);
/// <summary>
@@ -241,7 +241,7 @@ namespace Newtonsoft.Json.Linq
}
int index = _parent.IndexOfItem(this);
_parent.TryAddInternal(index + 1, content, false);
_parent.TryAddInternal(index + 1, content, false, copyAnnotations: true);
}
/// <summary>
@@ -256,7 +256,7 @@ namespace Newtonsoft.Json.Linq
}
int index = _parent.IndexOfItem(this);
_parent.TryAddInternal(index, content, false);
_parent.TryAddInternal(index, content, false, copyAnnotations: true);
}
/// <summary>
@@ -2443,7 +2443,17 @@ namespace Newtonsoft.Json.Linq
/// <returns>A new instance of the <see cref="JToken"/>.</returns>
public JToken DeepClone()
{
return CloneToken();
return CloneToken(settings: null);
}
/// <summary>
/// Creates a new instance of the <see cref="JToken"/>. All child tokens are recursively cloned.
/// </summary>
/// <param name="settings">A <see cref="JsonCloneSettings"/> object to configure cloning settings.</param>
/// <returns>A new instance of the <see cref="JToken"/>.</returns>
public JToken DeepClone(JsonCloneSettings settings)
{
return CloneToken(settings);
}
/// <summary>
+1 -1
View File
@@ -502,7 +502,7 @@ namespace Newtonsoft.Json.Linq
}
}
JToken value = tokenReader.CurrentToken!.CloneToken();
JToken value = tokenReader.CurrentToken!.CloneToken(settings: null);
if (_parent != null)
{
+11 -3
View File
@@ -57,6 +57,15 @@ namespace Newtonsoft.Json.Linq
_valueType = type;
}
internal JValue(JValue other, JsonCloneSettings? settings)
: this(other.Value, other.Type)
{
if (settings?.CopyAnnotations ?? true)
{
CopyAnnotations(this, other);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="JValue"/> class from another <see cref="JValue"/> object.
/// </summary>
@@ -64,7 +73,6 @@ namespace Newtonsoft.Json.Linq
public JValue(JValue other)
: this(other.Value, other.Type)
{
CopyAnnotations(this, other);
}
/// <summary>
@@ -557,9 +565,9 @@ namespace Newtonsoft.Json.Linq
}
#endif
internal override JToken CloneToken()
internal override JToken CloneToken(JsonCloneSettings? settings)
{
return new JValue(this);
return new JValue(this, settings);
}
/// <summary>
@@ -0,0 +1,32 @@
using System;
namespace Newtonsoft.Json.Linq
{
/// <summary>
/// Specifies the settings used when cloning JSON.
/// </summary>
public class JsonCloneSettings
{
internal static readonly JsonCloneSettings SkipCopyAnnotations = new JsonCloneSettings
{
CopyAnnotations = false
};
/// <summary>
/// Initializes a new instance of the <see cref="JsonCloneSettings"/> class.
/// </summary>
public JsonCloneSettings()
{
CopyAnnotations = true;
}
/// <summary>
/// Gets or sets a flag that indicates whether to copy annotations when cloning a <see cref="JToken"/>.
/// The default value is <c>true</c>.
/// </summary>
/// <value>
/// A flag that indicates whether to copy annotations when cloning a <see cref="JToken"/>.
/// </value>
public bool CopyAnnotations { get; set; }
}
}