- Notifications
You must be signed in to change notification settings - Fork58
Json Serializer ReferenceLoopHandling Issue? #285
Description
From@chrisckc on October 5, 2016 6:35
Hi,
I think i may have found an issue when using ReferenceLoopHandling.Error (which seems to be the default), in an MVC Web Api that i am working on. In my scenario i tried loading an entity and its related set of nested entities using the .Include() syntax. I then outputted the set of nested entities in the controller and did not see the result i expected. (I expected an exception be thrown)
Inspecting the resulting object graph before serialization shows that it contains self referencing loops because the context automatically linked up each nested entity back to its parent entity, which itself contains the same nested entities each linked back to their parent entities and so on.
When the controller then serialized the object graph using the json.net ReferenceLoopHandling.Error option, i expected to see an exception raised as per the json.net documentation.
What i found it that no exception is raised, instead the resulting Json outputted by the controller seems to be truncated at the point where i would expect that the circular reference was detected/encountered during serialization.
What i can see from looking at the Json is an array as the root element as expected,
but the array only contains the first entity representation (several are present before serialization) and this is only a partial representation, all of the properties that would normally be seen listed after the parent entity navigation property are missing from the representation, however the Json appears to be valid containing the correct closing braces etc.
Here is the pseudo code in the controller:
Owner owner = _dbContext.Owners.Include(item => item.Dogs).Where(item => item.OwnerId == id).FirstOrDefault();if (owner == null){ return NotFound();} else{ IEnumerable<Dogs> dogs = owner.Dogs.ToList(); return Ok(dogs);}Using the "ReferenceLoopHandling.Error" option, the resulting Json is like this:
[ { "dogId": "20000004-1111-1111-1111-111111111111", "dogName": "Fido", "dogPurchaseDate": "2016-09-29T04:31:00+00:00", "owner": { "ownerId": "10000000-1111-1111-1111-111111111111", "ownerName": "Fred", "dogs": [] } }]Instead of an exception, i have valid Json but with only the first dog entity outputted and there are Dog properties missing that would normally be listed after the "owner" property.
This looks as if the json.net exception is being swallowed somewhere, is this behaviour by design?
I would have thought it be essential that an exception is thrown when a loop is detected to avoid unpredictable output.
If i use ReferenceLoopHandling.Ignore, then all of the Dog entities are outputted correctly and owner property of each Dog entity includes the list of nested dog entities except for its own parent Dog entity, which is what i would expect as per the json.net documentation.
That kind of output is not what i want as it is very bloated, its just to show the difference in the behaviour of the 2 options during some testing.
I believe this is an EF/MVC issue rather than a json.net issue because if i do this:
//configure the same options used in startup.csJsonSerializerSettings settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), Formatting = Formatting.Indented, NullValueHandling = NullValueHandling.Include, MissingMemberHandling = MissingMemberHandling.Ignore, ReferenceLoopHandling = ReferenceLoopHandling.Error};settings.Converters.Add(new StringEnumConverter());//now try to serialize the dogs...string jsonString = JsonConvert.SerializeObject(dogs, settings);I correctly get an exception:Self referencing loop detected with type 'WebAPIApplication.Data.Dog'. Path '[0].Owner.Dogs'."
The controller is using json.net for serialization under the hood, so surely this exception should bubble up to the controller rather then be swallowed and result in a partial Json output.
Here is my json serializer startup.cs config:
public void ConfigureServices(IServiceCollection services){//other code.........builder.AddJsonOptions(o =>{ o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); o.SerializerSettings.Converters.Add(new StringEnumConverter()); o.SerializerSettings.Formatting = Formatting.Indented; o.SerializerSettings.NullValueHandling = NullValueHandling.Include; o.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore; o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Error;});project.json package versions:
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Npgsql.EntityFrameworkCore.PostgreSQL": "1.0.2-",
"Npgsql.EntityFrameworkCore.PostgreSQL.Design": "1.0.2-"
Looking at other issues logged here it seems like this has occurred previously and has been worked around/avoided so maybe the root cause has not yet been resolved. It also seems that the behaviour may have been different in older versions?
Issue 4160 mentions the Json truncation, however there seems to be some circular referencing going on in these 2 issues....
aspnet/Mvc#4160
That issue has been closed and refers to this one instead:
dotnet/efcore#4646
which then just changes the subject to view models etc. and is closed, referring back to 4160
This also seems similair:
dotnet/efcore#5429
Copied from original issue:dotnet/efcore#6684