Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object cannot by deserialized, because 'Collection was of a fixed size.'

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);
    }
like image 507
Rudis Avatar asked Oct 22 '13 07:10

Rudis


2 Answers

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());
like image 190
Brian Rogers Avatar answered Oct 09 '22 07:10

Brian Rogers


Thank, it works! I modified your code for more general usage in my example.

I suppose

  • is only one public constructor
  • 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);
        }
    }
    
like image 28
Rudis Avatar answered Oct 09 '22 05:10

Rudis