Skip to content

Commit

Permalink
Expose ReferenceResolver and rename ReferenceHandling to ReferenceHan…
Browse files Browse the repository at this point in the history
…dler (#36829) (#37296)

* Expose ReferenceResolver and rename ReferenceHandling to ReferenceHandler

* Address some feedback

* Address feedback

* Clean-up code

* Change messages in string.resx

* Add test for a badly implemented resolver

* Address feedback.
  • Loading branch information
jozkee authored Jun 4, 2020
1 parent d224df1 commit 74c06a9
Show file tree
Hide file tree
Showing 27 changed files with 496 additions and 259 deletions.
22 changes: 17 additions & 5 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults defaults) {
public bool PropertyNameCaseInsensitive { get { throw null; } set { } }
public System.Text.Json.JsonNamingPolicy? PropertyNamingPolicy { get { throw null; } set { } }
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
public System.Text.Json.Serialization.ReferenceHandling ReferenceHandling { get { throw null; } set { } }
public System.Text.Json.Serialization.ReferenceHandler? ReferenceHandler { get { throw null; } set { } }
public bool WriteIndented { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonConverter GetConverter(System.Type typeToConvert) { throw null; }
}
Expand Down Expand Up @@ -535,10 +535,22 @@ public JsonStringEnumConverter(System.Text.Json.JsonNamingPolicy? namingPolicy =
public override bool CanConvert(System.Type typeToConvert) { throw null; }
public override System.Text.Json.Serialization.JsonConverter CreateConverter(System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw null; }
}
public sealed partial class ReferenceHandling
public abstract partial class ReferenceHandler
{
internal ReferenceHandling() { }
public static System.Text.Json.Serialization.ReferenceHandling Default { get { throw null; } }
public static System.Text.Json.Serialization.ReferenceHandling Preserve { get { throw null; } }
protected ReferenceHandler() { }
public static System.Text.Json.Serialization.ReferenceHandler Preserve { get { throw null; } }
public abstract System.Text.Json.Serialization.ReferenceResolver CreateResolver();
}
public sealed partial class ReferenceHandler<T> : System.Text.Json.Serialization.ReferenceHandler where T : System.Text.Json.Serialization.ReferenceResolver, new()
{
public ReferenceHandler() { }
public override System.Text.Json.Serialization.ReferenceResolver CreateResolver() { throw null; }
}
public abstract partial class ReferenceResolver
{
protected ReferenceResolver() { }
public abstract void AddReference(string referenceId, object value);
public abstract string GetReference(object value, out bool alreadyExists);
public abstract object ResolveReference(string referenceId);
}
}
6 changes: 3 additions & 3 deletions src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@
<value>Either the JSON value is not in a supported format, or is out of bounds for a UInt16.</value>
</data>
<data name="SerializerCycleDetected" xml:space="preserve">
<value>A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of {0}. Consider using ReferenceHandling.Preserve on JsonSerializerOptions to support cycles.</value>
<value>A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of {0}. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles.</value>
</data>
<data name="EmptyStringToInitializeNumber" xml:space="preserve">
<value>Expected a number, but instead got empty string.</value>
Expand Down Expand Up @@ -480,7 +480,7 @@
<value>The '$id' and '$ref' metadata properties must be JSON strings. Current token type is '{0}'.</value>
</data>
<data name="MetadataInvalidPropertyWithLeadingDollarSign" xml:space="preserve">
<value>Properties that start with '$' are not allowed on preserve mode, either escape the character or turn off preserve references by setting ReferenceHandling to ReferenceHandling.Default.</value>
<value>Properties that start with '$' are not allowed on preserve mode, either escape the character or turn off preserve references by setting ReferenceHandler to null.</value>
</data>
<data name="MultipleMembersBindWithConstructorParameter" xml:space="preserve">
<value>Members '{0}' and '{1}' on type '{2}' cannot both bind with parameter '{3}' in constructor '{4}' on deserialization.</value>
Expand Down Expand Up @@ -527,4 +527,4 @@
<data name="DefaultIgnoreConditionInvalid" xml:space="preserve">
<value>The value cannot be 'JsonIgnoreCondition.Always'.</value>
</data>
</root>
</root>
7 changes: 5 additions & 2 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@
<Compile Include="System\Text\Json\Serialization\Converters\Value\UInt32Converter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\UInt64Converter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Value\UriConverter.cs" />
<Compile Include="System\Text\Json\Serialization\DefaultReferenceResolver.cs" />
<Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
<Compile Include="System\Text\Json\Serialization\JsonClassInfo.Cache.cs" />
Expand Down Expand Up @@ -162,10 +161,14 @@
<Compile Include="System\Text\Json\Serialization\MetadataPropertyName.cs" />
<Compile Include="System\Text\Json\Serialization\ParameterRef.cs" />
<Compile Include="System\Text\Json\Serialization\PooledByteBufferWriter.cs" />
<Compile Include="System\Text\Json\Serialization\PreserveReferenceHandler.cs" />
<Compile Include="System\Text\Json\Serialization\PreserveReferenceResolver.cs" />
<Compile Include="System\Text\Json\Serialization\PropertyRef.cs" />
<Compile Include="System\Text\Json\Serialization\ReadStack.cs" />
<Compile Include="System\Text\Json\Serialization\ReadStackFrame.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceHandling.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceHandler.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceHandlerOfT.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceResolver.cs" />
<Compile Include="System\Text\Json\Serialization\ReflectionEmitMemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\ReflectionMemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\StackFrameObjectState.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ internal sealed override bool OnTryRead(
ref ReadStack state,
[MaybeNullWhen(false)] out TCollection value)
{
bool shouldReadPreservedReferences = options.ReferenceHandling.ShouldReadPreservedReferences();

if (!state.SupportContinuation && !shouldReadPreservedReferences)
if (state.UseFastPath)
{
// Fast path that avoids maintaining state variables and dealing with preserved references.

Expand Down Expand Up @@ -148,7 +146,8 @@ internal sealed override bool OnTryRead(
}

// Handle the metadata properties.
if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.PropertyValue)
bool preserveReferences = options.ReferenceHandler != null;
if (preserveReferences && state.Current.ObjectState < StackFrameObjectState.PropertyValue)
{
if (JsonSerializer.ResolveMetadata(this, ref reader, ref state))
{
Expand All @@ -175,10 +174,10 @@ internal sealed override bool OnTryRead(
Debug.Assert(CanHaveIdMetadata);

value = (TCollection)state.Current.ReturnValue!;
if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, value))
{
ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
}
state.ReferenceResolver.AddReference(state.Current.MetadataId, value);
// Clear metadata name, if the next read fails
// we want to point the JSON path to the property's object.
state.Current.JsonPropertyName = null;
}

state.Current.ObjectState = StackFrameObjectState.CreatedObject;
Expand Down Expand Up @@ -214,7 +213,7 @@ internal sealed override bool OnTryRead(
state.Current.PropertyState = StackFramePropertyState.Name;

// Verify property doesn't contain metadata.
if (shouldReadPreservedReferences)
if (preserveReferences)
{
ReadOnlySpan<byte> propertyName = reader.GetSpan();
if (propertyName.Length > 0 && propertyName[0] == '$')
Expand Down Expand Up @@ -275,7 +274,7 @@ internal sealed override bool OnTryWrite(
state.Current.ProcessedStartToken = true;
writer.WriteStartObject();

if (options.ReferenceHandling.ShouldWritePreservedReferences())
if (options.ReferenceHandler != null)
{
if (JsonSerializer.WriteReferenceForObject(this, dictionary, ref state, writer) == MetadataPropertyName.Ref)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ internal override bool OnTryRead(
ref ReadStack state,
[MaybeNullWhen(false)] out TCollection value)
{
bool shouldReadPreservedReferences = options.ReferenceHandling.ShouldReadPreservedReferences();

if (!state.SupportContinuation && !shouldReadPreservedReferences)
if (state.UseFastPath)
{
// Fast path that avoids maintaining state variables and dealing with preserved references.

Expand Down Expand Up @@ -91,13 +89,14 @@ internal override bool OnTryRead(
{
// Slower path that supports continuation and preserved references.

bool preserveReferences = options.ReferenceHandler != null;
if (state.Current.ObjectState == StackFrameObjectState.None)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
state.Current.ObjectState = StackFrameObjectState.PropertyValue;
}
else if (shouldReadPreservedReferences)
else if (preserveReferences)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
Expand All @@ -113,7 +112,7 @@ internal override bool OnTryRead(
}

// Handle the metadata properties.
if (shouldReadPreservedReferences && state.Current.ObjectState < StackFrameObjectState.PropertyValue)
if (preserveReferences && state.Current.ObjectState < StackFrameObjectState.PropertyValue)
{
if (JsonSerializer.ResolveMetadata(this, ref reader, ref state))
{
Expand All @@ -137,10 +136,17 @@ internal override bool OnTryRead(
if (state.Current.MetadataId != null)
{
value = (TCollection)state.Current.ReturnValue!;
if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, value))
{
ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
}

// TODO: https://github.com/dotnet/runtime/issues/37168
//Separate logic for IEnumerable to call AddReference when the reader is at `$id`, in order to avoid remembering the last metadata.

// Remember the prior metadata and temporarily use '$id' to write it in the path in case AddReference throws
// in this case, the last property seen will be '$values' when we reach this point.
byte[]? lastMetadataProperty = state.Current.JsonPropertyName;
state.Current.JsonPropertyName = JsonSerializer.s_idPropertyName;

state.ReferenceResolver.AddReference(state.Current.MetadataId, value);
state.Current.JsonPropertyName = lastMetadataProperty;
}

state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.ElementClassInfo!.PropertyInfoForClassInfo;
Expand Down Expand Up @@ -247,13 +253,11 @@ internal sealed override bool OnTryWrite(Utf8JsonWriter writer, TCollection valu
}
else
{
bool shouldWritePreservedReferences = options.ReferenceHandling.ShouldWritePreservedReferences();

if (!state.Current.ProcessedStartToken)
{
state.Current.ProcessedStartToken = true;

if (!shouldWritePreservedReferences)
if (options.ReferenceHandler == null)
{
writer.WriteStartArray();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ internal class ObjectDefaultConverter<T> : JsonObjectConverter<T> where T : notn
{
internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, [MaybeNullWhen(false)] out T value)
{
bool shouldReadPreservedReferences = options.ReferenceHandling.ShouldReadPreservedReferences();
object obj;

if (!state.SupportContinuation && !shouldReadPreservedReferences)
if (state.UseFastPath)
{
// Fast path that avoids maintaining state variables and dealing with preserved references.

Expand Down Expand Up @@ -76,7 +75,7 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
// Handle the metadata properties.
if (state.Current.ObjectState < StackFrameObjectState.PropertyValue)
{
if (shouldReadPreservedReferences)
if (options.ReferenceHandler != null)
{
if (JsonSerializer.ResolveMetadata(this, ref reader, ref state))
{
Expand Down Expand Up @@ -106,10 +105,10 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
obj = state.Current.JsonClassInfo.CreateObject!()!;
if (state.Current.MetadataId != null)
{
if (!state.ReferenceResolver.AddReferenceOnDeserialize(state.Current.MetadataId, obj))
{
ThrowHelper.ThrowJsonException_MetadataDuplicateIdFound(state.Current.MetadataId, ref state);
}
state.ReferenceResolver.AddReference(state.Current.MetadataId, obj);
// Clear metadata name, if the next read fails
// we want to point the JSON path to the property's object.
state.Current.JsonPropertyName = null;
}

state.Current.ReturnValue = obj;
Expand Down Expand Up @@ -239,7 +238,7 @@ internal sealed override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSer
{
writer.WriteStartObject();

if (options.ReferenceHandling.ShouldWritePreservedReferences())
if (options.ReferenceHandler != null)
{
if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
{
Expand Down Expand Up @@ -294,7 +293,7 @@ internal sealed override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSer
{
writer.WriteStartObject();

if (options.ReferenceHandling.ShouldWritePreservedReferences())
if (options.ReferenceHandler != null)
{
if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ internal abstract partial class ObjectWithParameterizedConstructorConverter<T> :
{
internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, [MaybeNullWhen(false)] out T value)
{
bool shouldReadPreservedReferences = options.ReferenceHandling.ShouldReadPreservedReferences();
object obj;

if (!state.SupportContinuation && !shouldReadPreservedReferences)
if (state.UseFastPath)
{
// Fast path that avoids maintaining state variables.

Expand Down
Loading

0 comments on commit 74c06a9

Please sign in to comment.