Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JsonCloneSettings to disable copy annotations #2757

Merged
merged 9 commits into from Nov 14, 2022
129 changes: 129 additions & 0 deletions Src/Newtonsoft.Json.Tests/Linq/AnnotationsTests.cs
Expand Up @@ -303,11 +303,140 @@ public void MultipleAnnotationsAreCopied()
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()
Expand Down
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json.Tests/Linq/JRawTests.cs
Expand Up @@ -52,7 +52,7 @@ public void RawEquals()
public void RawClone()
{
JRaw r1 = new JRaw("raw1");
JToken r2 = r1.CloneToken();
JToken r2 = r1.DeepClone();

CustomAssert.IsInstanceOfType(typeof(JRaw), r2);
}
Expand Down
Expand Up @@ -324,7 +324,7 @@ public void DeserializeFromJToken()
]";

JToken t1 = JToken.Parse(json);
JToken t2 = t1.CloneToken();
JToken t2 = t1.DeepClone();

List<EmployeeReference> employees = t1.ToObject<List<EmployeeReference>>(JsonSerializer.Create(new JsonSerializerSettings
{
Expand Down
13 changes: 9 additions & 4 deletions Src/Newtonsoft.Json/Linq/JArray.cs
Expand Up @@ -66,7 +66,12 @@ public JArray()
/// </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)
{
}

Expand All @@ -93,9 +98,9 @@ internal override bool DeepEquals(JToken node)
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>
Expand Down Expand Up @@ -309,7 +314,7 @@ public int IndexOf(JToken item)
/// </exception>
public void Insert(int index, JToken item)
{
InsertItem(index, item, false);
InsertItem(index, item, false, copyAnnotations: true);
}

/// <summary>
Expand Down
12 changes: 9 additions & 3 deletions Src/Newtonsoft.Json/Linq/JConstructor.cs
Expand Up @@ -97,7 +97,13 @@ public JConstructor()
/// </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;
}
Expand Down Expand Up @@ -147,9 +153,9 @@ internal override bool DeepEquals(JToken node)
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>
Expand Down
46 changes: 28 additions & 18 deletions Src/Newtonsoft.Json/Linq/JContainer.cs
Expand Up @@ -106,19 +106,24 @@ internal JContainer()
{
}

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()
Expand Down Expand Up @@ -323,7 +328,7 @@ 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)
internal JToken EnsureParentToken(JToken? item, bool skipParentCheck, bool copyAnnotations)
{
if (item == null)
{
Expand All @@ -341,15 +346,20 @@ internal JToken EnsureParentToken(JToken? item, bool skipParentCheck)
// 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;
}

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;

Expand All @@ -360,7 +370,7 @@ internal virtual bool InsertItem(int index, JToken? item, bool skipParentCheck)

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
Expand Down Expand Up @@ -490,7 +500,7 @@ internal virtual void SetItem(int index, JToken? item)

CheckReentrancy();

item = EnsureParentToken(item, false);
item = EnsureParentToken(item, false, copyAnnotations: true);

ValidateToken(item, existing);

Expand Down Expand Up @@ -635,17 +645,17 @@ internal virtual void ValidateToken(JToken o, JToken? existing)
/// <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>
Expand All @@ -654,10 +664,10 @@ internal void AddAndSkipParentCheck(JToken token)
/// <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))
{
Expand All @@ -666,7 +676,7 @@ internal bool TryAddInternal(int index, object? content, bool skipParentCheck)
int multiIndex = index;
foreach (object c in enumerable)
{
TryAddInternal(multiIndex, c, skipParentCheck);
TryAddInternal(multiIndex, c, skipParentCheck, copyAnnotations);
multiIndex++;
}

Expand All @@ -676,7 +686,7 @@ internal bool TryAddInternal(int index, object? content, bool skipParentCheck)
{
JToken item = CreateFromContent(content);

return InsertItem(index, item, skipParentCheck);
return InsertItem(index, item, skipParentCheck, copyAnnotations);
}
}

Expand Down Expand Up @@ -963,7 +973,7 @@ int IList<JToken>.IndexOf(JToken item)

void IList<JToken>.Insert(int index, JToken item)
{
InsertItem(index, item, false);
InsertItem(index, item, false, copyAnnotations: true);
}

void IList<JToken>.RemoveAt(int index)
Expand Down Expand Up @@ -1046,7 +1056,7 @@ int IList.IndexOf(object? value)

void IList.Insert(int index, object? value)
{
InsertItem(index, EnsureValue(value), false);
InsertItem(index, EnsureValue(value), false, copyAnnotations: false);
}

bool IList.IsFixedSize => false;
Expand Down