Skip to content

Commit

Permalink
Add JsonCloneSettings to disable copy annotations (#2757)
Browse files Browse the repository at this point in the history
Co-authored-by: Sneha Shivakumar <snshivakumar@microsoft.com>
  • Loading branch information
JamesNK and snshivakumar committed Nov 14, 2022
1 parent d0a328e commit b13717a
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 49 deletions.
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

0 comments on commit b13717a

Please sign in to comment.