Reputation: 4582
In my current project I have the problem that I end up in an infinite loop when trying to convert an Item
or any of its subclasses like ArmorItem
.
To detect which type of Item
I have to deserialize I use a custom JsonConverter
called ItemConverter
.
Item.cs:
[JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(ItemConverter))]
public class Item
{
[JsonProperty("id")] public int Id { get; }
[JsonProperty("type")] public string ItemType { get; }
[JsonConstructor]
public Item(int id, string itemType)
{
Id = id;
ItemType = itemType;
}
}
ArmorItem.cs
[JsonObject(MemberSerialization.OptIn)]
public sealed class ArmorItem : Item
{
[JsonProperty("defense")] public int Defense { get; }
[JsonConstructor]
public ArmorItem(int id, string itemType, int defense) : base(id, itemType)
{
Defense = defense;
}
}
ItemConverter.cs
public sealed class ItemConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
switch (item["type"].Value<string>())
{
case "Armor":
return item.ToObject<ArmorItem>();
default:
return item.ToObject<Item>();
}
}
public override bool CanConvert(Type objectType)
=> typeof (Item).IsAssignableFrom(objectType);
}
I'm usually getting the json data from the web and directly use the WebResponse.GetResponseStream
stream to deserialize the data.
using (HttpWebResponse resp = (HttpWebResponse) req.GetResponse())
using (JsonTextReader reader = new JsonTextReader(new StreamReader(resp.GetResponseStream())))
{
return new JsonSerializer().Deserialize<Item>(reader);
}
I know why this loop occurs but I can't fix it.
However I noticed when deserializing the json data in a different way the problem doesn't occur.
(Item
was altered for this by removing the JsonConverter
attribute)
string json = "SOME JSON DATA HERE";
Item item = JsonConvert.DeserializeObject<Item>(json, new ItemConverter());
Unfortunately I cannot fix the existing code using streams and I don't want to temporarily store the incoming json data into a string to be able to use the working code.
Any ideas how to break the loop?
Upvotes: 9
Views: 4788
Reputation: 1
The answer we came up with was to cast the typed object to object
. Explanation of why this works below.
//CustomTypeConverter.cs
public class CustomTypeConverter: JsonConverter<CustomType>
{
//...
public override void Write(Utf8JsonWriter writer, CustomType value, JsonSerializerOptions options)
{
//the cast to (object) is crucial here in breaking the infinite loop
JsonSerializer.Serialize(writer, (object)value, options);
}
}
//CustomType.cs
[JsonConverter(typeof(CustomTypeConverter))]
public class CustomType
{
//...
}
Instead of:
Serialize(CustomType)
CustomType
is decorated with [CustomJsonConverter: JsonConverter<CustomType>]
, serialization invokes CustomJsonConverter.Write(CustomType)
CustomJsonConverter.Write()
invokes Serialize(CustomType)
Steps 3-4 change to become:
CustomJsonConverter.Write()
invokes Serialize((object)CustomObject)
Serialize((object) CustomObject)
actually serializes! yay.Upvotes: 0
Reputation: 161
Another way is to use serializer.Populate()
:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
switch (item["type"].Value<string>())
{
case "Armor":
var armorItem = new ArmorItem();
serializer.Populate(item.CreateReader(), armorItem);
return armorItem;
default:
var defaultItem = new Item();
serializer.Populate(item.CreateReader(), defaultItem);
return defaultItem;
}
}
More infos at https://gist.github.com/chrisoldwood/b604d69543a5fe5896a94409058c7a95
Upvotes: 7
Reputation: 5832
In short, you need to tell Json.net to deserialize your json via standard converter, not your custom one. While there's more than one way to do it, this is the one I can offer right now:
Remove JsonConverter(typeof(ItemConverter))
from Item
. This will allow item.ToObject<Item>()
to work properly.
Now you need to tell your outer deserialization to use the converter. To do that:
var settings = new JsonSerializerSettings()
{
Converters = new [] { new ItemConverter() }
};
return JsonSerializer.Create(settings).Deserialize<Item>(reader)
(actually, you can cache the settings)
Upvotes: 2