22

I have been unable to find a reasonable implementation for JsonConvert.WriteJson that allows me to insert a JSON property when serializing specific types. All my attempts have resulted in "JsonSerializationException : Self referencing loop detected with type XXX".

A little more background on the problem I'm trying to solve: I am using JSON as a config file format, and I'm using a JsonConverter to control the type resolution, serialization, and deserialization of my configuration types. Instead of using the $type property, I want to use more meaningful JSON values that are used to resolve the correct types.

In my pared-down example, here's some JSON text:

{
  "Target": "B",
  "Id": "foo"
}

where the JSON property "Target": "B" is used to determine that this object should be serialized into type B. This design might not seem that compelling given the simple example, but it does make the config file format more usable.

I also want the config files to be round-trippable. I have the deserialize case working, what I can't get working is the serialize case.

The root of my problem is that I can't find an implementation of JsonConverter.WriteJson that uses the standard JSON serialization logic, and doesn't throw a "Self referencing loop" exception. Here's my implementation:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    JProperty typeHintProperty = TypeHintPropertyForType(value.GetType());

    //BUG: JsonSerializationException : Self referencing loop detected with type 'B'. Path ''.
    // Same error occurs whether I use the serializer parameter or a separate serializer.
    JObject jo = JObject.FromObject(value, serializer); 
    if (typeHintProperty != null)
    {
        jo.AddFirst(typeHintProperty);
    }
    writer.WriteToken(jo.CreateReader());
}

The seems to me to be a bug in Json.NET, because there should be a way to do this. Unfortunately all the examples of JsonConverter.WriteJson that I've come across (eg Custom conversion of specific objects in JSON.NET) only provide custom serialization of a specific class, using the JsonWriter methods to write out individual objects and properties.

Here's the complete code for an xunit test that exhibits my problem (or see it here )

using System;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;

using Xunit;


public class A
{
    public string Id { get; set; }
    public A Child { get; set; }
}

public class B : A {}

public class C : A {}

/// <summary>
/// Shows the problem I'm having serializing classes with Json.
/// </summary>
public sealed class JsonTypeConverterProblem
{
    [Fact]
    public void ShowSerializationBug()
    {
        A a = new B()
              {
                  Id = "foo",
                  Child = new C() { Id = "bar" }
              };

        JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
        jsonSettings.ContractResolver = new TypeHintContractResolver();
        string json = JsonConvert.SerializeObject(a, Formatting.Indented, jsonSettings);
        Console.WriteLine(json);

        Assert.Contains(@"""Target"": ""B""", json);
        Assert.Contains(@"""Is"": ""C""", json);
    }

    [Fact]
    public void DeserializationWorks()
    {
        string json =
@"{
  ""Target"": ""B"",
  ""Id"": ""foo"",
  ""Child"": { 
        ""Is"": ""C"",
        ""Id"": ""bar"",
    }
}";

        JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
        jsonSettings.ContractResolver = new TypeHintContractResolver();
        A a = JsonConvert.DeserializeObject<A>(json, jsonSettings);

        Assert.IsType<B>(a);
        Assert.IsType<C>(a.Child);
    }
}

public class TypeHintContractResolver : DefaultContractResolver
{
    public override JsonContract ResolveContract(Type type)
    {
        JsonContract contract = base.ResolveContract(type);
        if ((contract is JsonObjectContract)
            && ((type == typeof(A)) || (type == typeof(B))) ) // In the real implementation, this is checking against a registry of types
        {
            contract.Converter = new TypeHintJsonConverter(type);
        }
        return contract;
    }
}


public class TypeHintJsonConverter : JsonConverter
{
    private readonly Type _declaredType;

    public TypeHintJsonConverter(Type declaredType)
    {
        _declaredType = declaredType;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == _declaredType;
    }

    // The real implementation of the next 2 methods uses reflection on concrete types to determine the declaredType hint.
    // TypeFromTypeHint and TypeHintPropertyForType are the inverse of each other.

    private Type TypeFromTypeHint(JObject jo)
    {
        if (new JValue("B").Equals(jo["Target"]))
        {
            return typeof(B);
        }
        else if (new JValue("A").Equals(jo["Hint"]))
        {
            return typeof(A);
        }
        else if (new JValue("C").Equals(jo["Is"]))
        {
            return typeof(C);
        }
        else
        {
            throw new ArgumentException("Type not recognized from JSON");
        }
    }

    private JProperty TypeHintPropertyForType(Type type)
    {
        if (type == typeof(A))
        {
            return new JProperty("Hint", "A");
        }
        else if (type == typeof(B))
        {
            return new JProperty("Target", "B");
        }
        else if (type == typeof(C))
        {
            return new JProperty("Is", "C");
        }
        else
        {
            return null;
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (! CanConvert(objectType))
        {
            throw new InvalidOperationException("Can't convert declaredType " + objectType + "; expected " + _declaredType);
        }

        // Load JObject from stream.  Turns out we're also called for null arrays of our objects,
        // so handle a null by returning one.
        var jToken = JToken.Load(reader);
        if (jToken.Type == JTokenType.Null)
            return null;
        if (jToken.Type != JTokenType.Object)
        {
            throw new InvalidOperationException("Json: expected " + _declaredType + "; got " + jToken.Type);
        }
        JObject jObject = (JObject) jToken;

        // Select the declaredType based on TypeHint
        Type deserializingType = TypeFromTypeHint(jObject);

        var target = Activator.CreateInstance(deserializingType);
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JProperty typeHintProperty = TypeHintPropertyForType(value.GetType());

        //BUG: JsonSerializationException : Self referencing loop detected with type 'B'. Path ''.
        // Same error occurs whether I use the serializer parameter or a separate serializer.
        JObject jo = JObject.FromObject(value, serializer); 
        if (typeHintProperty != null)
        {
            jo.AddFirst(typeHintProperty);
        }
        writer.WriteToken(jo.CreateReader());
    }

}
3
  • 1
    In your WriteJson method in your converter, have you tried removing the serializer parameter from the JObject.FromObject() call altogether? Seems to work in this fiddle Commented Sep 30, 2014 at 22:06
  • Thanks Brian - thank you for looking at this, and you're right, that fixes the Exception. It doesn't, however solve my problem, because I need to be able to do this in nested objects. I've updated the example to cover that. Or, see dotnetfiddle.net/b3yrEU (Fiddle is COOL!!)
    – crimbo
    Commented Oct 1, 2014 at 6:49
  • 1
    I would be interested to learn what you've ended up with. I'm having the same problem. Commented Jul 14, 2015 at 5:33

8 Answers 8

16

Calling JObject.FromObject() from within a converter on the same object being converted will result in a recursive loop, as you have seen. Normally the solution is to either (a) use a separate JsonSerializer instance inside the converter, or (b) serialize the properties manually, as James pointed out in his answer. Your case is a little special in that neither of these solutions really work for you: if you use a separate serializer instance that doesn't know about the converter then your child objects will not get their hint properties applied. And serializing completely manually doesn't work for a generalized solution, as you mentioned in your comments.

Fortunately, there is a middle ground. You can use a bit of reflection in your WriteJson method to get the object properties, then delegate from there to JToken.FromObject(). The converter will be called recursively as it should for the child properties, but not for the current object, so you don't get into trouble. One caveat with this solution: if you happen to have any [JsonProperty] attributes applied to the classes handled by this converter (A, B and C in your example), those attributes will not be respected.

Here is the updated code for the WriteJson method:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    JProperty typeHintProperty = TypeHintPropertyForType(value.GetType());

    JObject jo = new JObject();
    if (typeHintProperty != null)
    {
        jo.Add(typeHintProperty);
    }
    foreach (PropertyInfo prop in value.GetType().GetProperties())
    {
        if (prop.CanRead)
        {
            object propValue = prop.GetValue(value);
            if (propValue != null)
            {
                jo.Add(prop.Name, JToken.FromObject(propValue, serializer));
            }
        }
    }
    jo.WriteTo(writer);
}

Fiddle: https://dotnetfiddle.net/jQrxb8

9

Example of using a custom converter to take a property we ignore, break it down and add it's properties to it's parent object.:

public class ContextBaseSerializer : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(ContextBase).GetTypeInfo().IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contextBase = value as ContextBase;
        var valueToken = JToken.FromObject(value, new ForcedObjectSerializer());

        if (contextBase.Properties != null)
        {
            var propertiesToken = JToken.FromObject(contextBase.Properties);
            foreach (var property in propertiesToken.Children<JProperty>())
            {
                valueToken[property.Name] = property.Value;
            }
        }

        valueToken.WriteTo(writer);
    }
}

We must override the serializer so we can specify a custom resolver:

public class ForcedObjectSerializer : JsonSerializer
{
    public ForcedObjectSerializer()
        : base()
    {
        this.ContractResolver = new ForcedObjectResolver();
    }
}

And in the custom resolver we'll trash the Converter from the JsonContract, this will force the internal serializers to use the default object serializer:

public class ForcedObjectResolver : DefaultContractResolver
{
    public override JsonContract ResolveContract(Type type)
    {
        // We're going to null the converter to force it to serialize this as a plain object.
        var contract =  base.ResolveContract(type);
        contract.Converter = null;
        return contract;
    }
}

That should get you there, or close enough. :) I use this in https://github.com/RoushTech/SegmentDotNet/ which has test cases covering this use case (including nesting our custom serialized class), details on the discussion covering that here: https://github.com/JamesNK/Newtonsoft.Json/issues/386

1
  • 2
    This is easily the most underrated answer here. For what its worth, this wasn't a 100% perfect solution for me, as I really want to use all the settings from the original serializer. Check out this answer. You may consider refining this answer to reflect the benefits. Still, amazing work. Commented Jul 6, 2016 at 17:42
3

How about this:

public class TypeHintContractResolver : DefaultContractResolver
{

  protected override IList<JsonProperty> CreateProperties(Type type,
      MemberSerialization memberSerialization)
  {
    IList<JsonProperty> result = base.CreateProperties(type, memberSerialization);
    if (type == typeof(A))
    {
      result.Add(CreateTypeHintProperty(type,"Hint", "A"));
    }
    else if (type == typeof(B))
    {
      result.Add(CreateTypeHintProperty(type,"Target", "B"));
    }
    else if (type == typeof(C))
    {
      result.Add(CreateTypeHintProperty(type,"Is", "C"));
    }
    return result;
  }

  private JsonProperty CreateTypeHintProperty(Type declaringType, string propertyName, string propertyValue)
  {
    return new JsonProperty
    {
        PropertyType = typeof (string),
        DeclaringType = declaringType,
        PropertyName = propertyName,
        ValueProvider = new TypeHintValueProvider(propertyValue),
        Readable = false,
        Writable = true
    };
  }
}

The type value provider required for that can be as simple as this:

public class TypeHintValueProvider : IValueProvider
{

  private readonly string _value;
  public TypeHintValueProvider(string value)
  {
    _value = value;
  }

  public void SetValue(object target, object value)
  {        
  }

  public object GetValue(object target)
  {
    return _value;
  }

}

Fiddle: https://dotnetfiddle.net/DRNzz8

1
  • This looks like what Im after, but I want to use it for serializing but I can't get it to work (properties get added but don't appear in json on the browser). Should it work for serializing? Commented Oct 3, 2017 at 17:49
2

Brian's answer is great and should help the OP, but the answer has a couple of problems that others may run into, namely: 1) an overflow exception gets thrown when serializing array properties, 2) any static public properties will be emitted to JSON which you likely don't want.

Here is another version that tackles those problems:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    Type valueType = value.GetType();
    if (valueType.IsArray)
    {
        var jArray = new JArray();
        foreach (var item in (IEnumerable)value)
            jArray.Add(JToken.FromObject(item, serializer));

        jArray.WriteTo(writer);
    }
    else
    {
        JProperty typeHintProperty = TypeHintPropertyForType(value.GetType());

        var jObj = new JObject();
        if (typeHintProperty != null)
            jo.Add(typeHintProperty);

        foreach (PropertyInfo property in valueType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (property.CanRead)
            {
                object propertyValue = property.GetValue(value);
                if (propertyValue != null)
                    jObj.Add(property.Name, JToken.FromObject(propertyValue, serializer));
            }
        }

        jObj.WriteTo(writer);
    }
}
1
  • After 2 hours of working on this issue your answer finally saved me, with some adaptations this solved my issue. Thanks a lot! Commented Jul 10, 2020 at 9:36
2

Ran into this problem in 2019 :)

The answer is, if you don't want an @stackoverflow Don't Forget to override:

  • bool CanWrite
  • bool CanRead

    public class DefaultJsonConverter : JsonConverter
    {
        [ThreadStatic]
        private static bool _isReading;
    
        [ThreadStatic]
        private static bool _isWriting;
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            try
            {
                _isWriting = true;
    
                Property typeHintProperty = TypeHintPropertyForType(value.GetType());
    
                var jObject = JObject.FromObject(value, serializer);
                if (typeHintProperty != null)
                {
                    jObject.AddFirst(typeHintProperty);
                }
                writer.WriteToken(jObject.CreateReader());
            }
            finally
            {
                _isWriting = false;
            }
        }
    
        public override bool CanWrite
        {
            get
            {
                if (!_isWriting)
                    return true;
    
                _isWriting = false;
    
                return false;
            }
        }
    
        public override bool CanRead
        {
            get
            {
                if (!_isReading)
                    return true;
    
                _isReading = false;
    
                return false;
            }
        }
    
        public override bool CanConvert(Type objectType)
        {
            return true;
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            try
            {
                _isReading = true;
                return serializer.Deserialize(reader, objectType);
            }
            finally
            {
                _isReading = false;
            }
        }
    }
    

Credit to: https://github.com/RicoSuter/NJsonSchema/blob/master/src/NJsonSchema/Converters/JsonInheritanceConverter.cs

1

I've had a similar problem and here's what I do in the contract resolver

if (contract is JsonObjectContract && ShouldUseConverter(type))     
{
    if (contract.Converter is TypeHintJsonConverter)
    {
        contract.Converter = null;
    }
    else
    {
        contract.Converter = new TypeHintJsonConverter(type);
    }
}

This was the only way I found to avoid the StackOverflowException. Effectively every other call will not use the converter.

0

The serializer is calling into your converter which is then calling into the serializer which is calling into your converter, etc.

Either use a new instance of the serializer that doesn't have your converter with JObject.FromObject, or serialize the type's members manually.

1
  • 2
    Thanks. Serializing my type's members manually is not workable, as this is a generalized problem and I need it to work with any configured type. What I'm looking for is a way to intercept the normal serialization logic to insert the property. Ideally it would also use any other custom serialization settings in the original serializer, but I can get by without that for now. I'll try it using a second Serializer and JObject.
    – crimbo
    Commented Oct 7, 2014 at 19:39
-2

After having the same issue, and finding this and other similar questions, I found that the JsonConverter has an over-ridable property CanWrite.

Overriding this property to return false fixed this issue for me.

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

Hopefully, this will help others having the same problem.

2
  • Doing this via slightly modified solution from stackoverflow.com/a/9444519/1037948, I started getting NotImplementedExceptions from WriteJson recently, and no idea why. I suspect it's because I also overrode CanConvert. Just wanted to point out.
    – drzaus
    Commented Jul 1, 2015 at 15:56
  • 3
    If CanWrite returns false WriteJson is not called at all. Commented Jul 14, 2015 at 5:36

Not the answer you're looking for? Browse other questions tagged or ask your own question.