forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for preserve references on JSON (dotnet#655)
* Initial commit for JSON reference handling / preserve references * Remove ReferenceHandling.Ignore option * Code clean-up. * Remove stale tests. * Address PR feedback * Reference handling inline (dotnet#1) * Inline ReferenceHandling logic within main methods. * remove stale Handle*Ref methods * Refactor the code for Deserialization * Create methods to reuse code * Try to isolate Preserve logic as much as I can * Replaced Exceptions for ThrowHelper methods * Remove stale condition on $ref * Add AggressiveInlining to HandlePropertyNameDefault * Inline feature code in Serialization * Fix preserve references for ExtensionData * Split Reference dictionary into two, for (De)Serialize each. Add Reference EqualityComparer to Serialize dictionary. * Do not set PropertyName to s_missingProperty to avoid race condition issues. * Remove Asserts that compare against an exception message. * Set preserved array passing setPropertyDirectly = true to avoid issue when enumerable is already initialized. * Code clean-up. * Separate write code into WritePreservedObject and WriteReferenceObject * Address some PR feedback: * Switched ReferenceResolver to access it through a read-only property that initializes it the first time is tried to be accesed. * Renamed some methods and properties * Refactored WriteReference* methods. * Fixed Exception messages * Removed literal message comparison on tests. * * Add round-tripping coverage to suitable unit tests * Fix issues found with Round-tripping scenarios * Move DictionaryPropertyIsPreserved to EndProperty() in order to fix issue where two dictionary properties were next to each other and the DictionaryPropertyIsPreserved was not reset. * Add ReadStackFrame.IsNestedPreservedArray in order to identify preserved arrays nested and prevent using setPropertyDirectly on them. * Add nullability annotations * Code clean-up, reword comments and removed unnecesary properties in ReferenceHandling * Fix issue where an object that tries to map into an enumerable could skip the validation that prevents this. * Fix issue where the wrong type was passed into the throw helper for nested enumerable in HandleStartObject * Address PR comments. * Refactor flags on ReadStackFrame to avoid using unrelated fields for validation. * Consolidated MetadataPropertyName enum logic on read and write. * Reuse HandleStartObjectInEnumerable to handle arrays with metadata nested in a Dictionary. * Refactor code: * Change ReferenceResolver from class to struct to avoid one allocation. * Replace propertyName.ToArray() for constant arrays in order to avoid allocation. * Move some exception logic to the ThrowHelper class. * * Apply optimizations: * Use PropertyCacheArray on Values property lookup. * Make Comparer static field to avoid one allocation at runtime. * Document reasoning for DefaultReferenceResolver and ReferenreEqualsEqualityComparer * Address PR comments. * Addres more PR feedback: * Add documentation to DefaultReferenceResolver public methods. * Address PR feedback. * Adderss more PR suggestions. * * Move tests to Serialization namespace. * On $values, set PropertyName on state.Current.JsonPropertyName instead.
- Loading branch information
Showing
35 changed files
with
3,240 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
...libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultReferenceResolver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Licensed to the.NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Collections.Generic; | ||
|
||
namespace System.Text.Json.Serialization | ||
{ | ||
/// <summary> | ||
/// The default ReferenceResolver implementation to handle duplicate object references. | ||
/// </summary> | ||
/// <remarks> | ||
/// It is currently a struct to save one unnecessary allcation while (de)serializing. | ||
/// If we choose to expose the ReferenceResolver in a future, we may need to create an abstract class/interface and change this type to become a class that inherits from that abstract class/interface. | ||
/// </remarks> | ||
internal struct DefaultReferenceResolver | ||
{ | ||
private uint _referenceCount; | ||
private readonly Dictionary<string, object>? _referenceIdToObjectMap; | ||
private readonly Dictionary<object, string>? _objectToReferenceIdMap; | ||
|
||
public DefaultReferenceResolver(bool writing) | ||
{ | ||
_referenceCount = default; | ||
|
||
if (writing) | ||
{ | ||
// Comparer used here to always do a Reference Equality comparison on serialization which is where we use the objects as the TKey in our dictionary. | ||
_objectToReferenceIdMap = new Dictionary<object, string>(ReferenceEqualsEqualityComparer<object>.Comparer); | ||
_referenceIdToObjectMap = null; | ||
} | ||
else | ||
{ | ||
_referenceIdToObjectMap = new Dictionary<string, object>(); | ||
_objectToReferenceIdMap = null; | ||
} | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Adds an entry to the bag of references using the specified id and value. | ||
/// This method gets called when an $id metadata property from a JSON object is read. | ||
/// </summary> | ||
/// <param name="referenceId">The identifier of the respective JSON object or array.</param> | ||
/// <param name="value">The value of the respective CLR reference type object that results from parsing the JSON object.</param> | ||
public void AddReferenceOnDeserialize(string referenceId, object value) | ||
{ | ||
if (!JsonHelpers.TryAdd(_referenceIdToObjectMap!, referenceId, value)) | ||
{ | ||
ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(referenceId); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets the reference id of the specified value if exists; otherwise a new id is assigned. | ||
/// This method gets called before a CLR object is written so we can decide whether to write $id and the rest of its properties or $ref and step into the next object. | ||
/// The first $id value will be 1. | ||
/// </summary> | ||
/// <param name="value">The value of the CLR reference type object to get or add an id for.</param> | ||
/// <param name="referenceId">The id realated to the object.</param> | ||
/// <returns></returns> | ||
public bool TryGetOrAddReferenceOnSerialize(object value, out string referenceId) | ||
{ | ||
if (!_objectToReferenceIdMap!.TryGetValue(value, out referenceId!)) | ||
{ | ||
_referenceCount++; | ||
referenceId = _referenceCount.ToString(); | ||
_objectToReferenceIdMap.Add(value, referenceId); | ||
|
||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Resolves the CLR reference type object related to the specified reference id. | ||
/// This method gets called when $ref metadata property is read. | ||
/// </summary> | ||
/// <param name="referenceId">The id related to the returned object.</param> | ||
/// <returns></returns> | ||
public object ResolveReferenceOnDeserialize(string referenceId) | ||
{ | ||
if (!_referenceIdToObjectMap!.TryGetValue(referenceId, out object? value)) | ||
{ | ||
ThrowHelper.ThrowJsonException_MetadataReferenceNotFound(referenceId); | ||
} | ||
|
||
return value; | ||
} | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
...ries/System.Text.Json/src/System/Text/Json/Serialization/JsonPreservableArrayReference.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
namespace System.Text.Json | ||
{ | ||
/// <summary> | ||
/// JSON objects that contain metadata properties and the nested JSON array are wrapped into this class. | ||
/// </summary> | ||
/// <typeparam name="T">The original type of the enumerable.</typeparam> | ||
internal class JsonPreservableArrayReference<T> | ||
{ | ||
/// <summary> | ||
/// The actual enumerable instance being preserved is extracted when we finish processing the JSON object on HandleEndObject. | ||
/// </summary> | ||
public T Values { get; set; } = default!; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.