Skip to content

Commit b13717a

Browse files
JamesNKsnshivakumar
andauthoredNov 14, 2022
Add JsonCloneSettings to disable copy annotations (#2757)
Co-authored-by: Sneha Shivakumar <snshivakumar@microsoft.com>
1 parent d0a328e commit b13717a

13 files changed

+265
-49
lines changed
 

‎Src/Newtonsoft.Json.Tests/Linq/AnnotationsTests.cs

+129
Original file line numberDiff line numberDiff line change
@@ -303,11 +303,140 @@ public void MultipleAnnotationsAreCopied()
303303
Assert.AreEqual(0, o2.Annotations<Version>().Count());
304304
}
305305

306+
[Test]
307+
public void NestedAnnotationsAreCopied()
308+
{
309+
Version version = new Version(1, 2, 3, 4);
310+
311+
JObject o = new JObject();
312+
o.AddAnnotation("string!");
313+
o.AddAnnotation(version);
314+
315+
JValue v = new JValue(true);
316+
v.AddAnnotation("string!");
317+
v.AddAnnotation(version);
318+
319+
o["Item1"] = v;
320+
321+
JObject o2 = (JObject)o.DeepClone();
322+
Assert.AreEqual("string!", o2.Annotation<string>());
323+
Assert.AreEqual(version, o2.Annotation<Version>());
324+
325+
JValue v2 = (JValue)o2["Item1"];
326+
Assert.AreEqual("string!", v2.Annotation<string>());
327+
Assert.AreEqual(version, v2.Annotation<Version>());
328+
}
329+
330+
[Test]
331+
public void NestedAnnotationsAreCopiedWithDefault()
332+
{
333+
Version version = new Version(1, 2, 3, 4);
334+
JsonCloneSettings settings = new JsonCloneSettings();
335+
336+
JObject o = new JObject();
337+
o.AddAnnotation("string!");
338+
o.AddAnnotation(version);
339+
340+
JValue v = new JValue(true);
341+
v.AddAnnotation("string!");
342+
v.AddAnnotation(version);
343+
344+
o["Item1"] = v;
345+
346+
JObject o2 = (JObject)o.DeepClone(settings);
347+
Assert.AreEqual("string!", o2.Annotation<string>());
348+
Assert.AreEqual(version, o2.Annotation<Version>());
349+
350+
JValue v2 = (JValue)o2["Item1"];
351+
Assert.AreEqual("string!", v2.Annotation<string>());
352+
Assert.AreEqual(version, v2.Annotation<Version>());
353+
}
354+
355+
[Test]
356+
public void NestedAnnotationsAreNotCopiedWithSettingsCopyAnnotationsFalse()
357+
{
358+
Version version = new Version(1, 2, 3, 4);
359+
JsonCloneSettings settings = new JsonCloneSettings() { CopyAnnotations = false };
360+
361+
JObject o = new JObject();
362+
o.AddAnnotation("string!");
363+
o.AddAnnotation(version);
364+
365+
JValue v = new JValue(true);
366+
v.AddAnnotation("string!");
367+
v.AddAnnotation(version);
368+
369+
o["Item1"] = v;
370+
371+
JObject o2 = (JObject)o.DeepClone(settings);
372+
Assert.IsNull(o2.Annotation<string>());
373+
Assert.AreEqual(0, o2.Annotations<Version>().Count());
374+
375+
JValue v2 = (JValue)o2["Item1"];
376+
Assert.IsNull(v2.Annotation<string>());
377+
Assert.AreEqual(0, v2.Annotations<Version>().Count());
378+
}
379+
306380
private void AssertCloneCopy<T>(JToken t, T annotation) where T : class
307381
{
308382
Assert.AreEqual(annotation, t.DeepClone().Annotation<T>());
309383
}
310384

385+
[Test]
386+
public void MultipleAnnotationsAreNotCopiedWithSetting()
387+
{
388+
Version version = new Version(1, 2, 3, 4);
389+
JsonCloneSettings settings = new JsonCloneSettings() { CopyAnnotations = false };
390+
391+
JObject o = new JObject();
392+
o.AddAnnotation("string!");
393+
o.AddAnnotation(version);
394+
395+
JObject o2 = (JObject)o.DeepClone(settings);
396+
Assert.IsNull(o2.Annotation<string>());
397+
Assert.AreEqual(0, o2.Annotations<Version>().Count());
398+
399+
JArray a = new JArray();
400+
a.AddAnnotation("string!");
401+
a.AddAnnotation(version);
402+
403+
JArray a2 = (JArray)a.DeepClone(settings);
404+
Assert.IsNull(a2.Annotation<string>());
405+
Assert.AreEqual(0, a2.Annotations<Version>().Count());
406+
407+
JProperty p = new JProperty("test");
408+
p.AddAnnotation("string!");
409+
p.AddAnnotation(version);
410+
411+
JProperty p2 = (JProperty)p.DeepClone(settings);
412+
Assert.IsNull(p2.Annotation<string>());
413+
Assert.AreEqual(0, p2.Annotations<Version>().Count());
414+
415+
JRaw r = new JRaw("test");
416+
r.AddAnnotation("string!");
417+
r.AddAnnotation(version);
418+
419+
JRaw r2 = (JRaw)r.DeepClone(settings);
420+
Assert.IsNull(r2.Annotation<string>());
421+
Assert.AreEqual(0, r2.Annotations<Version>().Count());
422+
423+
JConstructor c = new JConstructor("test");
424+
c.AddAnnotation("string!");
425+
c.AddAnnotation(version);
426+
427+
JConstructor c2 = (JConstructor)c.DeepClone(settings);
428+
Assert.IsNull(c2.Annotation<string>());
429+
Assert.AreEqual(0, c2.Annotations<Version>().Count());
430+
431+
JValue v = new JValue("test");
432+
v.AddAnnotation("string!");
433+
v.AddAnnotation(version);
434+
435+
JValue v2 = (JValue)v.DeepClone(settings);
436+
Assert.IsNull(v2.Annotation<string>());
437+
Assert.AreEqual(0, v2.Annotations<Version>().Count());
438+
}
439+
311440
#if !NET20
312441
[Test]
313442
public void Example()

‎Src/Newtonsoft.Json.Tests/Linq/JRawTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void RawEquals()
5252
public void RawClone()
5353
{
5454
JRaw r1 = new JRaw("raw1");
55-
JToken r2 = r1.CloneToken();
55+
JToken r2 = r1.DeepClone();
5656

5757
CustomAssert.IsInstanceOfType(typeof(JRaw), r2);
5858
}

‎Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ public void DeserializeFromJToken()
324324
]";
325325

326326
JToken t1 = JToken.Parse(json);
327-
JToken t2 = t1.CloneToken();
327+
JToken t2 = t1.DeepClone();
328328

329329
List<EmployeeReference> employees = t1.ToObject<List<EmployeeReference>>(JsonSerializer.Create(new JsonSerializerSettings
330330
{

‎Src/Newtonsoft.Json/Linq/JArray.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ public JArray()
6666
/// </summary>
6767
/// <param name="other">A <see cref="JArray"/> object to copy from.</param>
6868
public JArray(JArray other)
69-
: base(other)
69+
: base(other, settings: null)
70+
{
71+
}
72+
73+
internal JArray(JArray other, JsonCloneSettings? settings)
74+
: base(other, settings)
7075
{
7176
}
7277

@@ -93,9 +98,9 @@ internal override bool DeepEquals(JToken node)
9398
return (node is JArray t && ContentsEqual(t));
9499
}
95100

96-
internal override JToken CloneToken()
101+
internal override JToken CloneToken(JsonCloneSettings? settings = null)
97102
{
98-
return new JArray(this);
103+
return new JArray(this, settings);
99104
}
100105

101106
/// <summary>
@@ -309,7 +314,7 @@ public int IndexOf(JToken item)
309314
/// </exception>
310315
public void Insert(int index, JToken item)
311316
{
312-
InsertItem(index, item, false);
317+
InsertItem(index, item, false, copyAnnotations: true);
313318
}
314319

315320
/// <summary>

‎Src/Newtonsoft.Json/Linq/JConstructor.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,13 @@ public JConstructor()
9797
/// </summary>
9898
/// <param name="other">A <see cref="JConstructor"/> object to copy from.</param>
9999
public JConstructor(JConstructor other)
100-
: base(other)
100+
: base(other, settings: null)
101+
{
102+
_name = other.Name;
103+
}
104+
105+
internal JConstructor(JConstructor other, JsonCloneSettings? settings)
106+
: base(other, settings)
101107
{
102108
_name = other.Name;
103109
}
@@ -147,9 +153,9 @@ internal override bool DeepEquals(JToken node)
147153
return (node is JConstructor c && _name == c.Name && ContentsEqual(c));
148154
}
149155

150-
internal override JToken CloneToken()
156+
internal override JToken CloneToken(JsonCloneSettings? settings = null)
151157
{
152-
return new JConstructor(this);
158+
return new JConstructor(this, settings);
153159
}
154160

155161
/// <summary>

‎Src/Newtonsoft.Json/Linq/JContainer.cs

+28-18
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,24 @@ internal JContainer()
106106
{
107107
}
108108

109-
internal JContainer(JContainer other)
109+
internal JContainer(JContainer other, JsonCloneSettings? settings)
110110
: this()
111111
{
112112
ValidationUtils.ArgumentNotNull(other, nameof(other));
113113

114+
bool copyAnnotations = settings?.CopyAnnotations ?? true;
115+
116+
if (copyAnnotations)
117+
{
118+
CopyAnnotations(this, other);
119+
}
120+
114121
int i = 0;
115122
foreach (JToken child in other)
116123
{
117-
TryAddInternal(i, child, false);
124+
TryAddInternal(i, child, false, copyAnnotations);
118125
i++;
119126
}
120-
121-
CopyAnnotations(this, other);
122127
}
123128

124129
internal void CheckReentrancy()
@@ -323,7 +328,7 @@ internal bool IsMultiContent([NotNullWhen(true)]object? content)
323328
return (content is IEnumerable && !(content is string) && !(content is JToken) && !(content is byte[]));
324329
}
325330

326-
internal JToken EnsureParentToken(JToken? item, bool skipParentCheck)
331+
internal JToken EnsureParentToken(JToken? item, bool skipParentCheck, bool copyAnnotations)
327332
{
328333
if (item == null)
329334
{
@@ -341,15 +346,20 @@ internal JToken EnsureParentToken(JToken? item, bool skipParentCheck)
341346
// the item is being added to the root parent of itself
342347
if (item.Parent != null || item == this || (item.HasValues && Root == item))
343348
{
344-
item = item.CloneToken();
349+
// Avoid allocating settings when copy annotations is false.
350+
JsonCloneSettings? settings = copyAnnotations
351+
? null
352+
: JsonCloneSettings.SkipCopyAnnotations;
353+
354+
item = item.CloneToken(settings);
345355
}
346356

347357
return item;
348358
}
349359

350360
internal abstract int IndexOfItem(JToken? item);
351361

352-
internal virtual bool InsertItem(int index, JToken? item, bool skipParentCheck)
362+
internal virtual bool InsertItem(int index, JToken? item, bool skipParentCheck, bool copyAnnotations)
353363
{
354364
IList<JToken> children = ChildrenTokens;
355365

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

361371
CheckReentrancy();
362372

363-
item = EnsureParentToken(item, skipParentCheck);
373+
item = EnsureParentToken(item, skipParentCheck, copyAnnotations);
364374

365375
JToken? previous = (index == 0) ? null : children[index - 1];
366376
// haven't inserted new token yet so next token is still at the inserting index
@@ -490,7 +500,7 @@ internal virtual void SetItem(int index, JToken? item)
490500

491501
CheckReentrancy();
492502

493-
item = EnsureParentToken(item, false);
503+
item = EnsureParentToken(item, false, copyAnnotations: true);
494504

495505
ValidateToken(item, existing);
496506

@@ -635,17 +645,17 @@ internal virtual void ValidateToken(JToken o, JToken? existing)
635645
/// <param name="content">The content to be added.</param>
636646
public virtual void Add(object? content)
637647
{
638-
TryAddInternal(ChildrenTokens.Count, content, false);
648+
TryAddInternal(ChildrenTokens.Count, content, false, copyAnnotations: true);
639649
}
640650

641651
internal bool TryAdd(object? content)
642652
{
643-
return TryAddInternal(ChildrenTokens.Count, content, false);
653+
return TryAddInternal(ChildrenTokens.Count, content, false, copyAnnotations: true);
644654
}
645655

646656
internal void AddAndSkipParentCheck(JToken token)
647657
{
648-
TryAddInternal(ChildrenTokens.Count, token, true);
658+
TryAddInternal(ChildrenTokens.Count, token, true, copyAnnotations: true);
649659
}
650660

651661
/// <summary>
@@ -654,10 +664,10 @@ internal void AddAndSkipParentCheck(JToken token)
654664
/// <param name="content">The content to be added.</param>
655665
public void AddFirst(object? content)
656666
{
657-
TryAddInternal(0, content, false);
667+
TryAddInternal(0, content, false, copyAnnotations: true);
658668
}
659669

660-
internal bool TryAddInternal(int index, object? content, bool skipParentCheck)
670+
internal bool TryAddInternal(int index, object? content, bool skipParentCheck, bool copyAnnotations)
661671
{
662672
if (IsMultiContent(content))
663673
{
@@ -666,7 +676,7 @@ internal bool TryAddInternal(int index, object? content, bool skipParentCheck)
666676
int multiIndex = index;
667677
foreach (object c in enumerable)
668678
{
669-
TryAddInternal(multiIndex, c, skipParentCheck);
679+
TryAddInternal(multiIndex, c, skipParentCheck, copyAnnotations);
670680
multiIndex++;
671681
}
672682

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

679-
return InsertItem(index, item, skipParentCheck);
689+
return InsertItem(index, item, skipParentCheck, copyAnnotations);
680690
}
681691
}
682692

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

964974
void IList<JToken>.Insert(int index, JToken item)
965975
{
966-
InsertItem(index, item, false);
976+
InsertItem(index, item, false, copyAnnotations: true);
967977
}
968978

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

10471057
void IList.Insert(int index, object? value)
10481058
{
1049-
InsertItem(index, EnsureValue(value), false);
1059+
InsertItem(index, EnsureValue(value), false, copyAnnotations: false);
10501060
}
10511061

10521062
bool IList.IsFixedSize => false;

0 commit comments

Comments
 (0)