-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Add support for preserve references on JSON #655
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
Merged
Merged
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
d3848c5
Initial commit for JSON reference handling / preserve references
jozkee 1007de8
Remove ReferenceHandling.Ignore option
jozkee 996e401
Code clean-up.
jozkee b1c684d
Remove stale tests.
jozkee f416d42
Address PR feedback
jozkee 1dccc50
Reference handling inline (#1)
jozkee 3774c1b
Fix preserve references for ExtensionData
jozkee b5aaca6
Split Reference dictionary into two, for (De)Serialize each.
jozkee 46ded84
Do not set PropertyName to s_missingProperty to avoid race condition …
jozkee 5628604
Remove Asserts that compare against an exception message.
jozkee 96a019b
Set preserved array passing setPropertyDirectly = true to avoid issue…
jozkee 56748f9
Code clean-up.
jozkee 561ee64
Separate write code into WritePreservedObject and WriteReferenceObject
jozkee 8700fcf
Address some PR feedback:
jozkee cfa7e58
* Add round-tripping coverage to suitable unit tests
jozkee af7d8c6
Merge branch 'master' of https://github.com/dotnet/runtime into Refer…
jozkee 2ec7985
Add nullability annotations
jozkee 42a44a9
Code clean-up, reword comments and removed unnecesary properties in R…
jozkee d7bded8
Fix issue where an object that tries to map into an enumerable could …
jozkee c965e2c
Fix issue where the wrong type was passed into the throw helper for n…
jozkee d02c296
Address PR comments.
jozkee b8e7345
Refactor flags on ReadStackFrame to avoid using unrelated fields for …
jozkee c4913a8
Consolidated MetadataPropertyName enum logic on read and write.
jozkee 8102079
Reuse HandleStartObjectInEnumerable to handle arrays with metadata ne…
jozkee c9d0f2d
Refactor code:
jozkee 4680580
Move some exception logic to the ThrowHelper class.
jozkee c0befe5
* Apply optimizations:
jozkee 53b12ad
Address PR comments.
jozkee 2b05e02
Addres more PR feedback:
jozkee da9cf04
Address PR feedback.
jozkee 2174770
Adderss more PR suggestions.
jozkee a0db554
* Move tests to Serialization namespace.
jozkee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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
68 changes: 68 additions & 0 deletions
68
...libraries/System.Text.Json/src/System/Text/Json/Serialization/DefaultReferenceResolver.cs
This file contains hidden or 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,68 @@ | ||
| // 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 | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| /// <summary> | ||
| /// Our ReferenceResolver implementation to handle references. | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </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 and change this type to become a class that inherits from such abstract. | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </remarks> | ||
| internal struct DefaultReferenceResolver | ||
| { | ||
| private uint _referenceCount; | ||
| private Dictionary<string, object>? _keyObjectMap; | ||
| private Dictionary<object, string>? _objectKeyMap; | ||
|
|
||
| public DefaultReferenceResolver(bool isWrite) | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| _referenceCount = default; | ||
|
|
||
| if (isWrite) | ||
| { | ||
| _objectKeyMap = new Dictionary<object, string>(ReferenceEqualsEqualityComparer<object>.Comparer); | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| _keyObjectMap = null; | ||
| } | ||
| else | ||
| { | ||
| _keyObjectMap = new Dictionary<string, object>(); | ||
| _objectKeyMap = null; | ||
| } | ||
| } | ||
|
|
||
| public void AddReference(string key, object value) | ||
| { | ||
| if (!JsonHelpers.TryAdd(_keyObjectMap!, key, value)) | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(key); | ||
| } | ||
| } | ||
|
|
||
| public string GetOrAddReference(object value, out bool alreadyExists) | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
jozkee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| alreadyExists = _objectKeyMap!.TryGetValue(value, out string? key); | ||
| if (!alreadyExists) | ||
| { | ||
| key = (++_referenceCount).ToString(); | ||
| _objectKeyMap.Add(value, key); | ||
| } | ||
|
|
||
| return key!; | ||
| } | ||
|
|
||
| public object ResolveReference(string key) | ||
| { | ||
| if (!_keyObjectMap!.TryGetValue(key, out object? value)) | ||
| { | ||
| ThrowHelper.ThrowJsonException_MetadataReferenceNotFound(key); | ||
| } | ||
|
|
||
| return value!; | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
13 changes: 13 additions & 0 deletions
13
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPreservedReference.cs
This file contains hidden or 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,13 @@ | ||
| // 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 | ||
| { | ||
| internal class JsonPreservedReference<T> | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| #nullable disable | ||
| public T Values { get; set; } | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| #nullable enable | ||
| } | ||
| } | ||
This file contains hidden or 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 hidden or 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 hidden or 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
106 changes: 106 additions & 0 deletions
106
...System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleMetadata.cs
This file contains hidden or 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,106 @@ | ||
| // 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.Diagnostics; | ||
|
|
||
| namespace System.Text.Json | ||
| { | ||
| public static partial class JsonSerializer | ||
| { | ||
| private static void HandleMetadataPropertyValue(ref Utf8JsonReader reader, ref ReadStack state) | ||
jozkee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| if (reader.TokenType != JsonTokenType.String) | ||
| { | ||
| ThrowHelper.ThrowJsonException_MetadataValueWasNotString(); | ||
| } | ||
|
|
||
| MetadataPropertyName metadata = state.Current.MetadataProperty; | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| string key = reader.GetString()!; | ||
|
|
||
| if (metadata == MetadataPropertyName.Id) | ||
| { | ||
| state.ReferenceResolver.AddReference(key, GetValueToPreserve(ref state)); | ||
| } | ||
| else if (metadata == MetadataPropertyName.Ref) | ||
| { | ||
| state.Current.ReferenceId = key; | ||
| } | ||
|
|
||
| state.Current.ReadMetadataValue = false; | ||
| } | ||
|
|
||
| private static object GetValueToPreserve(ref ReadStack state) | ||
| { | ||
| return state.Current.IsProcessingProperty(ClassType.Dictionary) ? | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| state.Current.JsonPropertyInfo!.GetValueAsObject(state.Current.ReturnValue)! : state.Current.ReturnValue!; | ||
| } | ||
|
|
||
| internal static MetadataPropertyName GetMetadataPropertyName(ReadOnlySpan<byte> propertyName, ref ReadStack state, ref Utf8JsonReader reader) | ||
| { | ||
| if (propertyName[0] == '$') | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| switch (propertyName.Length) | ||
| { | ||
| case 3: | ||
| if (propertyName[1] == 'i' && | ||
| propertyName[2] == 'd') | ||
| { | ||
| return MetadataPropertyName.Id; | ||
| } | ||
| break; | ||
|
|
||
| case 4: | ||
| if (propertyName[1] == 'r' && | ||
| propertyName[2] == 'e' && | ||
| propertyName[3] == 'f') | ||
| { | ||
| return MetadataPropertyName.Ref; | ||
| } | ||
| break; | ||
|
|
||
| case 7: | ||
| // Only Preserved Arrays are allowed to read $values as metadata. | ||
| if (state.Current.IsPreservedArray && | ||
| propertyName[1] == 'v' && | ||
jozkee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| propertyName[2] == 'a' && | ||
| propertyName[3] == 'l' && | ||
| propertyName[4] == 'u' && | ||
| propertyName[5] == 'e' && | ||
| propertyName[6] == 's') | ||
| { | ||
| return MetadataPropertyName.Values; | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| ThrowHelper.ThrowJsonException_MetadataInvalidPropertyWithLeadingSign(propertyName, ref state, in reader); | ||
| } | ||
|
|
||
| return MetadataPropertyName.NoMetadata; | ||
| } | ||
|
|
||
| private static void HandleReference(ref ReadStack state) | ||
| { | ||
| object referenceValue = state.ReferenceResolver.ResolveReference(state.Current.ReferenceId); | ||
| if (state.Current.IsProcessingProperty(ClassType.Dictionary)) | ||
| { | ||
| ApplyObjectToEnumerable(referenceValue, ref state, setPropertyDirectly: true); | ||
| state.Current.EndProperty(); | ||
| } | ||
| else | ||
| { | ||
| state.Current.ReturnValue = referenceValue; | ||
| HandleEndObject(ref state); | ||
| } | ||
|
|
||
| state.Current.ShouldHandleReference = false; | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| private static JsonPropertyInfo GetValuesFromJsonPreservedReference(ref ReadStackFrame current) | ||
| { | ||
| Debug.Assert(current.JsonClassInfo!.PropertyCacheArray![0] == current.JsonClassInfo.PropertyCache!["Values"]); | ||
jozkee marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return current.JsonClassInfo.PropertyCacheArray[0]; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.