Skip to content

Support resolve reference for typeof(object) on deserialize#38979

Merged
jozkee merged 1 commit intodotnet:masterfrom
jozkee:fix_1776
Jul 13, 2020
Merged

Support resolve reference for typeof(object) on deserialize#38979
jozkee merged 1 commit intodotnet:masterfrom
jozkee:fix_1776

Conversation

@jozkee
Copy link
Member

@jozkee jozkee commented Jul 9, 2020

Fixes #1776

Analyze the JsonElement to be returned, determines if it can be interpreted as a reference ({"$ref":"1"}), lookup for the reference and returns it instead.

This is performed to enable round-tripping on scenarios where a property that is typeof(object) is a reference to an instance of another type e.g:

private class Node
{
    public Node Child { get; set; }
    public object Sibling { get; set; }
}

This change also grants parity with Json.NET in said scenario.

@jozkee jozkee requested review from layomia and steveharter July 9, 2020 04:14
@jkotas jkotas changed the title Support reseolve reference for typeof(object) on deserialize Support resolve reference for typeof(object) on deserialize Jul 9, 2020
@jozkee jozkee self-assigned this Jul 9, 2020
@jozkee jozkee added this to the 5.0.0 milestone Jul 9, 2020
// There are more properties in an object with $ref.
ThrowHelper.ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties();
}
else if (property.EscapedNameEquals(s_refPropertyName))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we lose the validation that the metadata in the payload was the unescaped literal "$ref". Do we care?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm not really, unlike NameEquals; EscapedNameEquals will compare against the raw bytes, i.e. it won't unescape the name.

ref reader);
}

if (CanBePolymorphic && options.ReferenceHandler != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could another approach be to intercept the call to the Object/JsonElementConverter (when the ref handling feature is on) and just read the $ref metadata directly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but we would have to create a copy of the reader that we would need to throw away if the payload doesn't met the required conditions (being an object, contain only the $ref property). Since this is an edge case, I think this approach is cleaner.

JsonTestHelper.AssertJsonEqual(expectedjson, serialized);

memoryStream.Position = 0;
await TestDeserialization<TElement>(memoryStream, expectedjson, type, options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this line deleted?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is still there on line 72, I added the condition if (options.ReferenceHandler == null || !GetTypesNonRoundtrippableWithReferenceHandler().Contains(type)) to skip it given that it won't work if the root type uses object type elements and has a $ref object.

typeof(GenericIReadOnlyDictionaryWrapper<string, TElement>)
};

// Non-generic types cannot roundtrip when they contain a $ref written on serialization and they are the root type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's a minimal code sample to highlight this behavior?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key1 will be parsed as a JsonElement and when key2 tries to look for the reference, it will not find it.

using System;
using System.Collections;
using System.Text.Json;
using System.Text.Json.Serialization;

public class Program
{
  public static void Main()
  {
    var foo = new Foo();
    Hashtable h = new Hashtable();
    h.Add("key1", foo);
    h.Add("key2", foo);

    var opts = new JsonSerializerOptions();
    opts.ReferenceHandler = ReferenceHandler.Preserve;

    string json = JsonSerializer.Serialize(h, opts);
    Console.WriteLine(json);
    // {"$id":"1","key1":{"$id":"2"},"key2":{"$ref":"2"}}

    Hashtable copyOfh = JsonSerializer.Deserialize<Hashtable>(json, opts);
  }
}

@jozkee jozkee merged commit d44d638 into dotnet:master Jul 13, 2020
@jozkee jozkee deleted the fix_1776 branch July 13, 2020 17:42
@ghost ghost locked as resolved and limited conversation to collaborators Dec 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserved references should avoid calling object converter

3 participants