diff --git a/Src/Newtonsoft.Json.Tests/Linq/AnnotationsTests.cs b/Src/Newtonsoft.Json.Tests/Linq/AnnotationsTests.cs index 28af83764..7a8eb44ee 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/AnnotationsTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/AnnotationsTests.cs @@ -303,11 +303,140 @@ public void MultipleAnnotationsAreCopied() Assert.AreEqual(0, o2.Annotations().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()); + Assert.AreEqual(version, o2.Annotation()); + + JValue v2 = (JValue)o2["Item1"]; + Assert.AreEqual("string!", v2.Annotation()); + Assert.AreEqual(version, v2.Annotation()); + } + + [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()); + Assert.AreEqual(version, o2.Annotation()); + + JValue v2 = (JValue)o2["Item1"]; + Assert.AreEqual("string!", v2.Annotation()); + Assert.AreEqual(version, v2.Annotation()); + } + + [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()); + Assert.AreEqual(0, o2.Annotations().Count()); + + JValue v2 = (JValue)o2["Item1"]; + Assert.IsNull(v2.Annotation()); + Assert.AreEqual(0, v2.Annotations().Count()); + } + private void AssertCloneCopy(JToken t, T annotation) where T : class { Assert.AreEqual(annotation, t.DeepClone().Annotation()); } + [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()); + Assert.AreEqual(0, o2.Annotations().Count()); + + JArray a = new JArray(); + a.AddAnnotation("string!"); + a.AddAnnotation(version); + + JArray a2 = (JArray)a.DeepClone(settings); + Assert.IsNull(a2.Annotation()); + Assert.AreEqual(0, a2.Annotations().Count()); + + JProperty p = new JProperty("test"); + p.AddAnnotation("string!"); + p.AddAnnotation(version); + + JProperty p2 = (JProperty)p.DeepClone(settings); + Assert.IsNull(p2.Annotation()); + Assert.AreEqual(0, p2.Annotations().Count()); + + JRaw r = new JRaw("test"); + r.AddAnnotation("string!"); + r.AddAnnotation(version); + + JRaw r2 = (JRaw)r.DeepClone(settings); + Assert.IsNull(r2.Annotation()); + Assert.AreEqual(0, r2.Annotations().Count()); + + JConstructor c = new JConstructor("test"); + c.AddAnnotation("string!"); + c.AddAnnotation(version); + + JConstructor c2 = (JConstructor)c.DeepClone(settings); + Assert.IsNull(c2.Annotation()); + Assert.AreEqual(0, c2.Annotations().Count()); + + JValue v = new JValue("test"); + v.AddAnnotation("string!"); + v.AddAnnotation(version); + + JValue v2 = (JValue)v.DeepClone(settings); + Assert.IsNull(v2.Annotation()); + Assert.AreEqual(0, v2.Annotations().Count()); + } + #if !NET20 [Test] public void Example() diff --git a/Src/Newtonsoft.Json.Tests/Linq/JRawTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JRawTests.cs index cfeb102b3..57b7c3a88 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JRawTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JRawTests.cs @@ -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); } diff --git a/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs index 2073dbcfe..e7d00dfb1 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/MetadataPropertyHandlingTests.cs @@ -324,7 +324,7 @@ public void DeserializeFromJToken() ]"; JToken t1 = JToken.Parse(json); - JToken t2 = t1.CloneToken(); + JToken t2 = t1.DeepClone(); List employees = t1.ToObject>(JsonSerializer.Create(new JsonSerializerSettings { diff --git a/Src/Newtonsoft.Json/Linq/JArray.cs b/Src/Newtonsoft.Json/Linq/JArray.cs index 241a56045..c589e211b 100644 --- a/Src/Newtonsoft.Json/Linq/JArray.cs +++ b/Src/Newtonsoft.Json/Linq/JArray.cs @@ -66,7 +66,12 @@ public JArray() /// /// A object to copy from. public JArray(JArray other) - : base(other) + : base(other, settings: null) + { + } + + internal JArray(JArray other, JsonCloneSettings? settings) + : base(other, settings) { } @@ -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); } /// @@ -309,7 +314,7 @@ public int IndexOf(JToken item) /// public void Insert(int index, JToken item) { - InsertItem(index, item, false); + InsertItem(index, item, false, copyAnnotations: true); } /// diff --git a/Src/Newtonsoft.Json/Linq/JConstructor.cs b/Src/Newtonsoft.Json/Linq/JConstructor.cs index f2aa86182..703dbbc12 100644 --- a/Src/Newtonsoft.Json/Linq/JConstructor.cs +++ b/Src/Newtonsoft.Json/Linq/JConstructor.cs @@ -97,7 +97,13 @@ public JConstructor() /// /// A object to copy from. 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 @@ 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); } /// diff --git a/Src/Newtonsoft.Json/Linq/JContainer.cs b/Src/Newtonsoft.Json/Linq/JContainer.cs index f9447cd5d..e19be3619 100644 --- a/Src/Newtonsoft.Json/Linq/JContainer.cs +++ b/Src/Newtonsoft.Json/Linq/JContainer.cs @@ -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() @@ -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) { @@ -341,7 +346,12 @@ 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; @@ -349,7 +359,7 @@ internal JToken EnsureParentToken(JToken? item, bool skipParentCheck) 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 children = ChildrenTokens; @@ -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 @@ -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); @@ -635,17 +645,17 @@ internal virtual void ValidateToken(JToken o, JToken? existing) /// The content to be added. 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); } /// @@ -654,10 +664,10 @@ internal void AddAndSkipParentCheck(JToken token) /// The content to be added. 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 @@ 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++; } @@ -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); } } @@ -963,7 +973,7 @@ int IList.IndexOf(JToken item) void IList.Insert(int index, JToken item) { - InsertItem(index, item, false); + InsertItem(index, item, false, copyAnnotations: true); } void IList.RemoveAt(int index) @@ -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; diff --git a/Src/Newtonsoft.Json/Linq/JObject.cs b/Src/Newtonsoft.Json/Linq/JObject.cs index 872c8ae7c..fab460da4 100644 --- a/Src/Newtonsoft.Json/Linq/JObject.cs +++ b/Src/Newtonsoft.Json/Linq/JObject.cs @@ -93,7 +93,12 @@ public JObject() /// /// A object to copy from. public JObject(JObject other) - : base(other) + : base(other, settings: null) + { + } + + internal JObject(JObject other, JsonCloneSettings? settings) + : base(other, settings) { } @@ -135,7 +140,7 @@ internal override int IndexOfItem(JToken? item) 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 @@ internal override bool InsertItem(int index, JToken? item, bool skipParentCheck) 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 @@ internal void InternalPropertyChanging(JProperty childProperty) #endif } - internal override JToken CloneToken() + internal override JToken CloneToken(JsonCloneSettings? settings) { - return new JObject(this); + return new JObject(this, settings); } /// diff --git a/Src/Newtonsoft.Json/Linq/JProperty.cs b/Src/Newtonsoft.Json/Linq/JProperty.cs index 3c738cf52..b8c7ace6a 100644 --- a/Src/Newtonsoft.Json/Linq/JProperty.cs +++ b/Src/Newtonsoft.Json/Linq/JProperty.cs @@ -173,7 +173,7 @@ public JToken Value if (_content._token == null) { - InsertItem(0, newValue, false); + InsertItem(0, newValue, false, copyAnnotations: true); } else { @@ -187,7 +187,13 @@ public JToken Value /// /// A object to copy from. 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 @@ internal override int IndexOfItem(JToken? item) 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 @@ internal override bool InsertItem(int index, JToken? item, bool skipParentCheck) 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 @@ internal override bool DeepEquals(JToken node) 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); } /// diff --git a/Src/Newtonsoft.Json/Linq/JRaw.cs b/Src/Newtonsoft.Json/Linq/JRaw.cs index 02ca51fd1..02ca96d3c 100644 --- a/Src/Newtonsoft.Json/Linq/JRaw.cs +++ b/Src/Newtonsoft.Json/Linq/JRaw.cs @@ -38,7 +38,12 @@ public partial class JRaw : JValue /// /// A object to copy from. public JRaw(JRaw other) - : base(other) + : base(other, settings: null) + { + } + + internal JRaw(JRaw other, JsonCloneSettings? settings) + : base(other, settings) { } @@ -67,9 +72,9 @@ public static JRaw Create(JsonReader reader) } } - internal override JToken CloneToken() + internal override JToken CloneToken(JsonCloneSettings? settings) { - return new JRaw(this); + return new JRaw(this, settings); } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JToken.cs b/Src/Newtonsoft.Json/Linq/JToken.cs index 336d748aa..27f84d8ba 100644 --- a/Src/Newtonsoft.Json/Linq/JToken.cs +++ b/Src/Newtonsoft.Json/Linq/JToken.cs @@ -131,7 +131,7 @@ public JToken Root } } - internal abstract JToken CloneToken(); + internal abstract JToken CloneToken(JsonCloneSettings? settings); internal abstract bool DeepEquals(JToken node); /// @@ -241,7 +241,7 @@ public void AddAfterSelf(object? content) } int index = _parent.IndexOfItem(this); - _parent.TryAddInternal(index + 1, content, false); + _parent.TryAddInternal(index + 1, content, false, copyAnnotations: true); } /// @@ -256,7 +256,7 @@ public void AddBeforeSelf(object? content) } int index = _parent.IndexOfItem(this); - _parent.TryAddInternal(index, content, false); + _parent.TryAddInternal(index, content, false, copyAnnotations: true); } /// @@ -2443,7 +2443,17 @@ object ICloneable.Clone() /// A new instance of the . public JToken DeepClone() { - return CloneToken(); + return CloneToken(settings: null); + } + + /// + /// Creates a new instance of the . All child tokens are recursively cloned. + /// + /// A object to configure cloning settings. + /// A new instance of the . + public JToken DeepClone(JsonCloneSettings settings) + { + return CloneToken(settings); } /// diff --git a/Src/Newtonsoft.Json/Linq/JTokenWriter.cs b/Src/Newtonsoft.Json/Linq/JTokenWriter.cs index 344ae8343..618063e24 100644 --- a/Src/Newtonsoft.Json/Linq/JTokenWriter.cs +++ b/Src/Newtonsoft.Json/Linq/JTokenWriter.cs @@ -502,7 +502,7 @@ internal override void WriteToken(JsonReader reader, bool writeChildren, bool wr } } - JToken value = tokenReader.CurrentToken!.CloneToken(); + JToken value = tokenReader.CurrentToken!.CloneToken(settings: null); if (_parent != null) { diff --git a/Src/Newtonsoft.Json/Linq/JValue.cs b/Src/Newtonsoft.Json/Linq/JValue.cs index 09ed1fd42..c2f03991c 100644 --- a/Src/Newtonsoft.Json/Linq/JValue.cs +++ b/Src/Newtonsoft.Json/Linq/JValue.cs @@ -57,6 +57,15 @@ internal JValue(object? value, JTokenType type) _valueType = type; } + internal JValue(JValue other, JsonCloneSettings? settings) + : this(other.Value, other.Type) + { + if (settings?.CopyAnnotations ?? true) + { + CopyAnnotations(this, other); + } + } + /// /// Initializes a new instance of the class from another object. /// @@ -64,7 +73,6 @@ internal JValue(object? value, JTokenType type) public JValue(JValue other) : this(other.Value, other.Type) { - CopyAnnotations(this, other); } /// @@ -557,9 +565,9 @@ private static bool Operation(ExpressionType operation, object? objA, object? ob } #endif - internal override JToken CloneToken() + internal override JToken CloneToken(JsonCloneSettings? settings) { - return new JValue(this); + return new JValue(this, settings); } /// diff --git a/Src/Newtonsoft.Json/Linq/JsonCloneSettings.cs b/Src/Newtonsoft.Json/Linq/JsonCloneSettings.cs new file mode 100644 index 000000000..02f1351cc --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonCloneSettings.cs @@ -0,0 +1,32 @@ +using System; + +namespace Newtonsoft.Json.Linq +{ + /// + /// Specifies the settings used when cloning JSON. + /// + public class JsonCloneSettings + { + internal static readonly JsonCloneSettings SkipCopyAnnotations = new JsonCloneSettings + { + CopyAnnotations = false + }; + + /// + /// Initializes a new instance of the class. + /// + public JsonCloneSettings() + { + CopyAnnotations = true; + } + + /// + /// Gets or sets a flag that indicates whether to copy annotations when cloning a . + /// The default value is true. + /// + /// + /// A flag that indicates whether to copy annotations when cloning a . + /// + public bool CopyAnnotations { get; set; } + } +} \ No newline at end of file