James Wood James Wood - 3 months ago 11
C# Question

Json.net Collection was of a fixed size

I am trying to serialise and de-serialise the following classes using Json.net.

public class OperationBase
{
}

public class OperationCreate : OperationBase
{
public string Entity
{
get;
private set;
}

public IReadOnlyCollection<string> Attributes
{
get;
private set;
}

public OperationCreate(string entity, params string[] attributes)
{
Contract.Requires(entity != null);
Contract.Requires(attributes != null);

Entity = entity;
Attributes = attributes;
}
}

public class OperationUpdate : OperationCreate
{
public OperationUpdate(string entity, params string[] attributes)
: base(entity, attributes)
{
}
}

public class OperationAssign : OperationUpdate
{
public OperationAssign(string entity)
: base(entity, "ownerid")
{
}
}


Using the following code.

public void SerialiseTest<T>(T t)
{
string serialised = JsonConvert.SerializeObject(t, Formatting.Indented, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
});

ITraceWriter traceWriter = new MemoryTraceWriter();

T deserialised = JsonConvert.DeserializeObject<T>(serialised, new JsonSerializerSettings()
{
TraceWriter = traceWriter,
TypeNameHandling = TypeNameHandling.All,
});

deserialised.ShouldBeEquivalentTo(t);
}

[TestMethod]
public void Test()
{
OperationCreate create = new OperationCreate("Create", new string[] { "ownerid" });

OperationUpdate update = new OperationUpdate("Update", new string[] { "ownerid" });

OperationAssign assign = new OperationAssign("Test");

SerialiseTest(create);
SerialiseTest(update);
SerialiseTest(assign); //Exception!
}


I can correctly serialise and de-serialise the
OperationCreate
and
OperationUpdate
, however I receive this error on
OperationAssign
.

System.NotSupportedException: Collection was of a fixed size.


I don't understand why or how I can debug this further.
OperationAssign
is basically just passing parameters to the base classes (
OperationCreate
and
OperationUpdate
) which can be serialised and de-serialised successfully.

How can I correct this problem?

Serialised
OperationAssign


{
"$type": "Woodswork.Crm.Documenter.Data.Operations.OperationAssign, Woodswork.Crm.Documenter",
"Entity": "Test",
"Attributes": {
"$type": "System.String[], mscorlib",
"$values": [
"ownerid"
]
}
}


TraceWriter


2016-09-05T22:14:44.813 Verbose Resolved type 'Woodswork.Crm.Documenter.Data.Operations.OperationAssign, Woodswork.Crm.Documenter' to Woodswork.Crm.Documenter.Data.Operations.OperationAssign. Path '$type', line 2, position 95.
2016-09-05T22:14:44.813 Info Deserializing Woodswork.Crm.Documenter.Data.Operations.OperationAssign using creator with parameters: Entity. Path 'Entity', line 3, position 11.
2016-09-05T22:14:44.813 Verbose Resolved type 'System.String[], mscorlib' to System.String[]. Path 'Attributes.$type', line 5, position 40.
2016-09-05T22:14:44.813 Info Started deserializing System.String[]. Path 'Attributes.$values', line 6, position 16.
2016-09-05T22:14:44.813 Info Finished deserializing System.String[]. Path 'Attributes.$values', line 8, position 5.
2016-09-05T22:14:44.815 Info Started deserializing Woodswork.Crm.Documenter.Data.Operations.OperationAssign. Path '', line 10, position 1.
2016-09-05T22:14:44.824 Error Error deserializing Woodswork.Crm.Documenter.Data.Operations.OperationAssign. Collection was of a fixed size. Path '', line 10, position 1.


Alternative
OperationAssign
constructor


This also results in the same error.

public OperationAssign(string entity)
: base(entity, new string[] { "ownerid" })
{
}

dbc dbc
Answer

The problem is that you are serializing then trying to deserialize the Attributes collection of OperationAssign - but Json.NET has no way to deserialize it, as the collection is read-only and the property is not publicly settable.

Json.NET is able to successfully deserialize the base class OperationUpdate because it has a single constructor that is parameterized and with a parameter with the same name (modulo case) as the property, namely attributes. In such a case Json.NET will invoke the constructor and pass in the value of the "attributes" deserialized from the JSON file. Unfortunately the derived class omits this constructor, so deserialization fails.

You have several approaches to work around this:

  1. Add a constructor with the appropriate parameter, and mark with with [JsonConstructor]. It could be private:

    public class OperationAssign : OperationUpdate
    {
        [JsonConstructor]
        OperationAssign(string entity, params string[] attributes) : this(entity)
        {
        }
    
        public OperationAssign(string entity)
            : base(entity, "ownerid")
        {   
        }
    }
    

    You could ignore the value of the deserialized attributes if you want. The parameter need simply be present.

  2. Suppress serialization of the attributes in the derived class using conditional property serialization:

    public class OperationCreate : OperationBase
    {
        public string Entity
        {
            get;
            private set;
        }
    
        public IReadOnlyCollection<string> Attributes
        {
            get;
            private set;
        }
    
        public virtual bool ShouldSerializeAttributes() { return true; }
    
        public OperationCreate(string entity, params string[] attributes)
        {
            Contract.Requires(entity != null);
            Contract.Requires(attributes != null);
    
            Entity = entity;
            Attributes = attributes;
        }
    }
    
    public class OperationAssign : OperationUpdate
    {
        public OperationAssign(string entity)
            : base(entity, "ownerid")
        {   
        }
    
        public override bool ShouldSerializeAttributes() { return false; }
    }
    

    The base class must support this via a virtual ShouldSerializeAttributes() method.

  3. Mark the property with [JsonProperty] in the base class. This will force the private setter to be called:

    public class OperationCreate : OperationBase
    {
        public string Entity
        {
            get;
            private set;
        }
    
        [JsonProperty]
        public IReadOnlyCollection<string> Attributes
        {
            get;
            private set;
        }
    
        public virtual bool ShouldSerializeAttributes() { return true; }
    
        public OperationCreate(string entity, params string[] attributes)
        {
            Contract.Requires(entity != null);
            Contract.Requires(attributes != null);
    
            Entity = entity;
            Attributes = attributes;
        }
    }
    

Sample fiddle showing the options.