I have easy example of my real code. I need serialize to JSON and deserialize back object of class TestClass
, which is derived from Letters
. Both classes have constructor with parameter.
public class TestClass : Letters
{
public string[] Names { get; private set; }
public TestClass(string[] names)
: base(names)
// : base(new [] { "A", "B", })
// : base(names.Select(a => a.Substring(0, 1)).ToArray())
{
Names = names;
}
}
public abstract class Letters
{
public string[] FirstLetters { get; private set; }
protected Letters(string[] letters)
{
FirstLetters = letters;
}
}
Object of TestClass
is serialized to valid JSON, but when I try it deserialize back to object, NotSupportedException is throw with message Collection was of a fixed size.
Here is my test
[Fact]
public void JsonNamesTest()
{
var expected = new TestClass(new [] { "Alex", "Peter", "John", });
var serialized = JsonConvert.SerializeObject(expected);
Console.WriteLine(serialized);
Assert.False(string.IsNullOrWhiteSpace(serialized));
var actual = JsonConvert.DeserializeObject<TestClass>(serialized);
AssertEx.PrimitivePropertiesEqual(expected, actual);
}
Json.Net needs all classes to have a parameterless constructor in order to deserialize them, otherwise it doesn't know how to call the constructor. One way to get around this without changing your classes is to make a custom JsonConverter
that will create the object instance from the JSON. For example:
class TestClassConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(TestClass) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string[] names = jo["Names"].ToObject<string[]>();
return new TestClass(names);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Then, deserialize your class like this and it should work:
var actual = JsonConvert.DeserializeObject<TestClass>(serialized, new TestClassConverter());
Thank, it works! I modified your code for more general usage in my example.
I suppose
serialized parameters have same name as constructor parameters (ignore case)
public class ParametersContructorConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Letters).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
var contructor = objectType.GetConstructors().FirstOrDefault();
if (contructor == null)
{
return serializer.Deserialize(reader);
}
var parameters = contructor.GetParameters();
var values = parameters.Select(p => jo.GetValue(p.Name, StringComparison.InvariantCultureIgnoreCase).ToObject(p.ParameterType)).ToArray();
return contructor.Invoke(values);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With