简体   繁体   中英

Load objects with circular references to Blazor Wasm with System.Text.Json

Problem

I'm using Blazor WASM which communicates with RESTful API built with ASP.NET Core. I need to load objects with circular references.

With default serialization settings, this produces System.Text.Json.JsonException:

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 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions 
to support cycles.

My attempt

I've added the following lines to ConfigureServices() in my Startup.cs on the server:

services.AddControllersWithViews().AddJsonOptions(options => {
   options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
});

And modified the request on the client:

data = await Http.GetFromJsonAsync<My.Shared.DataObject[]>(
    "api/GetDataObjects", 
    new System.Text.Json.JsonSerializerOptions() { 
        ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve
    }
);

When I run this, the server returns reasonable response: 回复

But the client throws System.Text.Json.JsonException when parsing the response:

Cannot parse a JSON object containing metadata properties like '$id' into an array or immutable 
collection type. Type 'My.Shared.DataObject[]'. 

Path: $.$id | LineNumber: 0 | BytePositionInLine: 7

Why is this happening? How to fix it?

The error message is self-explanatory:

Cannot parse a JSON object containing metadata properties like '$id' into an array or immutable collection type. Type 'My.Shared.DataObject[]' .

This confirmed by the docs :

For value types, immutable types, and arrays, no reference metadata is serialized. On deserialization, an exception is thrown if $ref or $id is found.

Instead of an array, you need to deserialize to a List<DataObject> :

data = await Http.GetFromJsonAsync<List<My.Shared.DataObject>>(
    "api/GetDataObjects", 
    new System.Text.Json.JsonSerializerOptions() { 
        ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve
    }
);

Notes:

  • The restriction was likely implemented because arrays and read-only collections can only be fully constructed after their items are read in - but ReferenceHandler is indented to support back-references from children to parents as well as recursive self-references from the object to itself. And it's unclear how to implement such functionality for read-only collections or immutable objects that can only be constructed after their child items are constructed.

    Json.NET has a similar restriction, see Cannot preserve reference to array or readonly list, or list created from a non-default constructor for details.

  • If you do not need reference preservation for collections, you can serialize your collection as an array and $id information will not be included for the array itself. (Its items will still contain the required $id properties.)

    Ie on your server side, you must be sending a List<My.Shared.DataObject> over the wire. If instead you send a My.Shared.DataObject [] the root container will be a simple JSON array, not a JSON object with reference information and a nested $values array.

    For comparison, whereas Json.NET supports preserving references only for objects and not collections by setting PreserveReferencesHandling.Objects , System.Text.Json does not appear to provide this level of control via options.

  • You can always convert the List<DataObject> to an array afterwards with ToArray() .

Demo fiddle here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM