bklaric
bklaric

Reputation: 477

Self referencing loop detected where there is none

I have a data model made of a TransformationPlanModel which in turn contains an IEnumerable<TransformationModel>. TransformationPlanModel is the root data transfer object. TransformationModel is an abstract base class for other transformation models.

Snipped code for TransformationPlanModel:

public class TransformationPlanModel
{
    // snip
    public IEnumerable<TransformationModel> Transformations { get; set; }
}

Snipped code for TransformationModel:

public abstract class TransformationModel
{
    //snip
    public string Key { get; set; }
}

One of concrete transformation models is RemoveRowsTransformationModel:

public class RemoveRowsTransformationModel : TransformationModel
{
    public const string KeyConst = "removeRows";

    public int TopRowsCount { get; set; }
    public int BottomRowsCount { get; set; }

    public RemoveRowsTransformationModel()
    {
        Key = KeyConst;
    }

    //snip
}

I've written a custom JsonConverter which converts the TransformationModel inheritance tree. The implementation of JsonWrite method is:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    JObject.FromObject(value, serializer).WriteTo(writer);
}

The custom converter is used in an ASP.NET Core API application and is added to the services in the Startup.ConfigureServices method like so:

services.AddMvc().AddJsonOptions(options =>
{
    options.SerializerSettings.Converters.Add(new TransformationConverter());
});

When a TransformationPlanModel is returned as a response from an API endpoint and is serialized, a Newtonsoft.Json.JsonSerializationException is thrown containing the message: "Self referencing loop detected with type '(...).RemoveRowsTransformationModel'". What causes this exception? I fail to see where the self referencing loop is. I don't know if it matters, but I'm using .NET Core 2.0 Preview 1.

Interestingly enough, when an overload of JObject.FromObject without the serializer parameter is used in the WriteJson method, it works properly:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    JObject.FromObject(value).WriteTo(writer);
}

This however resets the serialization settings, like camel case property naming. I've also tried ignoring the circular references in Json.Net settings, but no transformations are serialized then.

Upvotes: 2

Views: 703

Answers (1)

Brian Rogers
Brian Rogers

Reputation: 129707

The "self-referencing loop" the error is referring to in this case is your converter calling itself. The serializer has settings which direct it to use your converter. Your converter calls JObject.FromObject(), passing in the serializer. JObject.FromObject uses the serializer to to try to create a JObject from your model. The serializer then calls your converter again, and so on. Json.Net detects this recursive loop and throws an exception.

One possible solution is to use a new serializer instance, copying all the settings from the original serializer except for the converter:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    JsonSerializer ser2 = new JsonSerializer();
    foreach (PropertyInfo prop in typeof(JsonSerializer).GetProperties()
            .Where(p => p.Name != nameof(JsonSerializer.Converters)))
    {
        prop.SetValue(ser2, prop.GetValue(serializer));
    }
    JObject.FromObject(value, ser2).WriteTo(writer);
}

However, since your WriteJson method doesn't appear to do anything other than just loading the object and immediately writing it out without any changes, a better solution is simply to override CanWrite to return false. Then WriteJson will not be called at all and the default serialization will be used instead, which seems to be what you want to happen here anyway.

public override bool CanWrite
{
    get { return false; }
}

Upvotes: 4

Related Questions