问题
I have the following class which is perfectly serializable:
public class Form {
public IList<IControl> Controls { get; set; }
}
public class ControlA : IControl {}
public class ControlB : IControl {}
It serializes without $type
information but I have a custom JsonConverter
which is able to deserialize all implementations of IControl
:
internal class MyJsonConverter : CustomCreationConverter<IControl> {}
It works fine for a scenario like this:
[JsonConverter(typeof(MyJsonConverter ))]
public IControl MyControl {get;set;}
However, I cannot apply the same JsonConverterAttribute
to my Form.Controls
property:
"Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path 'Controls', line 5, position 15."
How do I instruct the deserializer to use MyJsonConverter
for items inside Form.Controls
collection?
回答1:
You can use [JsonProperty(ItemConverterType = typeof(MyJsonConverter))] to apply the converter to the items in the collection:
public class Form
{
[JsonProperty(ItemConverterType = typeof(MyJsonConverter))]
public IList<IControl> Controls { get; set; }
}
回答2:
I am not sure what your MyJsonConverter
actually does however as a basic example you should only need to provide JsonSerializerSettings and setting the TypeNameHandling property to TypeNameHandling.All
This simple example works.
public interface IControl { }
public class Form
{
public IList<IControl> Controls { get; set; }
}
public class ControlA : IControl { }
public class ControlB : IControl { }
static void Main(string[] args)
{
var form = new Form();
form.Controls = new List<IControl>();
form.Controls.Add(new ControlA());
form.Controls.Add(new ControlB());
var json = JsonConvert.SerializeObject(form, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
var obj = JsonConvert.DeserializeObject<Form>(json, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
}
Edit
After clarifications that no the type handling is not used therefore no $type
property we have to get a bit more creative and read the raw jSON
. And construct the object in an adhoc way. Here is an example of the customer serializer.
internal class MyJsonConverter : CustomCreationConverter<IControl>
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var controlType = jObject["CustomProperty"]?.Value<string>();
IControl control = null;
if (!string.IsNullOrWhiteSpace(controlType))
{
switch (controlType.ToLowerInvariant())
{
case "controla":
control = Activator.CreateInstance(typeof(ControlA)) as IControl;
break;
case "controlb":
control = Activator.CreateInstance(typeof(ControlB)) as IControl;
break;
}
}
if (controlType == null)
throw new SerializationException($"Unable to deserialize property. {controlType}");
serializer.Populate(jObject.CreateReader(), control);
return control;
}
public override IControl Create(Type objectType)
{
return null;
}
}
Basically as we are depnding on a property in the IControl
interface (which has been ommited in the question) we will parse the json manually and get a reference to the property CustomProperty
If this property exsits with a valid string value (or you can use any other value you wish) we then create our IControl
manually.
Finally the piece that handles the deserialization is the final line serializer.Populate()
Full Test Case:
internal class MyJsonConverter : CustomCreationConverter<IControl>
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var controlType = jObject["CustomProperty"]?.Value<string>();
IControl control = null;
if (!string.IsNullOrWhiteSpace(controlType))
{
switch (controlType.ToLowerInvariant())
{
case "controla":
control = Activator.CreateInstance(typeof(ControlA)) as IControl;
break;
case "controlb":
control = Activator.CreateInstance(typeof(ControlB)) as IControl;
break;
}
}
if (controlType == null)
throw new SerializationException($"Unable to deserialize property. {controlType}");
serializer.Populate(jObject.CreateReader(), control);
return control;
}
public override IControl Create(Type objectType)
{
return null;
}
}
[JsonConverter(typeof(MyJsonConverter))]
public interface IControl
{
string CustomProperty { get; set; }
}
public class Form
{
public IList<IControl> Controls { get; set; }
}
public class ControlA : IControl
{
public string CustomProperty { get; set; } = "ControlA";
}
public class ControlB : IControl
{
public string CustomProperty { get; set; } = "ControlB";
}
static void Main(string[] args)
{
var form = new Form();
form.Controls = new List<IControl>();
form.Controls.Add(new ControlA());
form.Controls.Add(new ControlB());
var json = JsonConvert.SerializeObject(form);
var obj = JsonConvert.DeserializeObject<Form>(json);
}
来源:https://stackoverflow.com/questions/41733314/deserializing-a-collection-of-interfaces