Json.NET deserialization of Tuple<...> inside another type doesn't work?

Tar picture Tar · Jan 12, 2015 · Viewed 7.3k times · Source

Using Json.net, deserializing a type that contains a Tuple<...> doesn't work (serialization works, but deserialization doesn't):

[TestMethod]
public void Test()
{
    var orig = new TupleHolder("what????", true);
    var json = JsonConvert.SerializeObject(orig);
    Assert.AreEqual("{\"Tup\":{\"Item1\":\"what????\",\"Item2\":true}}", json);
    // great! serialization works like a charm! now let's test deserialization:
    var dupl = JsonConvert.DeserializeObject<TupleHolder>(json);

    Assert.AreEqual("ZZZ", dupl.Tup.Item1); // pass! but it should be "what????"... what????
    Assert.AreEqual(false, dupl.Tup.Item2); // pass! but it should be "true", right???

    Assert.AreEqual(orig.Tup.Item1, dupl.Tup.Item1); // fail!
    Assert.AreEqual(orig.Tup.Item2, dupl.Tup.Item2); // fail!
}

public class TupleHolder
{
    public Tuple<string, bool> Tup { get; set; }
    public TupleHolder() { Tup = new Tuple<string, bool>("ZZZ", false); }
    public TupleHolder(string s, bool b) { Tup = new Tuple<string, bool>(s, b); }
}

Funny thing is that direct deserialization of Tuple<...> does work:

[TestMethod]
public void Test2()
{
    var orig = new Tuple<string, bool>("ABC", true);
    var json = JsonConvert.SerializeObject(orig);
    var dupl = JsonConvert.DeserializeObject<Tuple<string, bool>>(json);
    Assert.AreEqual(orig, dupl); // direct deserialization of Tuple<...> works.
}

Is it a Json.NET bug or am I missing here something?

Answer

S&#233;bastien Sevrin picture Sébastien Sevrin · Apr 21, 2016

The answer provided by Remi helped me. I took his TupleConverter and made it generic for a 2-tuple. The concept is the same for any N-tuple.

I leave it here in case it helps someone.

public class TupleConverter<U, V> : Newtonsoft.Json.JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(Tuple<U, V>) == objectType;
    }

    public override object ReadJson(
        Newtonsoft.Json.JsonReader reader,
        Type objectType,
        object existingValue,
        Newtonsoft.Json.JsonSerializer serializer)
    {
        if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
            return null;

        var jObject = Newtonsoft.Json.Linq.JObject.Load(reader);

        var target = new Tuple<U, V>(
            jObject["m_Item1"].ToObject<U>(), jObject["m_Item2"].ToObject<V>());

        return target;
    }

    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

Note: My Tuple was JSON serialized with m_Item1 and m_Item2, so I had to change jObject["ItemX"] to jObject["m_ItemX"]

Usage example with a List<Tuple<int, User>>:

string result = "String to deserialize";
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new TupleConverter<int, User>());
List<Tuple<int, User>> users = JsonConvert.DeserializeObject<List<Tuple<int, User>>>(result, settings);