1

I have the following model structure :

public class Step : ActivityElement
{
    public Step()
    {
        this.Id = Guid.NewGuid();
        this.Label = "Basic Step";
        this.Type = "step";
    }

    public Guid Id { get; set; }
    public string Label { get; set; }
    public Input Input { get; set; }
}

public abstract class Input
{
    public string Type { get; set; }
    public object Value { get; set; }
}

public class GridInput : Input
{
    public GridInput()
    {
        this.Type = "grid";
        this.GridHeader = new GridHeader();
        this.RowList = new List<GridRow>();
    }

    public GridHeader GridHeader { get; set; }
    public List<GridRow> RowList { get; set; }
}

public class GridRow
{
    public GridRow()
    {
        this.IsDeleted = false;
        this.CellList = new List<GridCell>();
    }

    public List<GridCell> CellList { get; set; }
    public bool IsDeleted { get; set; }
}

public class GridCell
{
    public GridCell()
    {
        this.name = "not set yet";
        this.Input = null;
    }

    public string name { get; set; }
    public Input Input { get; set; }
}

Here is the JSON that I am trying to deserialize

{
    "Id":"e833ceae-57e5-4c52-8d56-b047790cb7c7",
    "Label":"Grid time!",
    "Input":
    {
        "GridHeader":{"ColumnDefinitionList":[{"field":"column1","title":"Column 1"},{"field":"column2","title":"Column 2"},{"field":"column3","title":"Column 3"},{"field":"column4","title":"Column 4"}]},
        "RowList":
        [
            {
                "CellList":
                [
                    {"name":"column1","Input":{"Type":"text","Value":"cell 1 row 1"}},
                    {"name":"column2","Input":{"Type":"text","Value":"cell 2 row 1"}},
                    {"name":"column3","Input":{"Type":"text","Value":"cell 3 row 1"}},
                    {"name":"column4","Input":{"Type":"text","Value":"cell 4 row 1"}}
                ],
                "IsDeleted":false
            },
            {
                "CellList":
                [
                    {"name":"column1","Input":{"Type":"text","Value":"cell 1 row 2"}},
                    {"name":"column2","Input":{"Type":"text","Value":"cell 2 row 2"}},
                    {"name":"column3","Input":{"Type":"text","Value":"cell 3 row 2"}},
                    {"name":"column4","Input":{"Type":"text","Value":"cell 4 row 2"}}
                ],
                "IsDeleted":false
            }
        ],
        "Type":"grid",
        "Value":null
    },
    "DisplayPosition":2,
    "Type":"step"
}

Here is the custom deserializer that makes sure the GridInput is the object created as the Input :

public abstract class JsonCreationConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject
        T target = Create(objectType, jObject);

        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }
}
public class InputJsonConverter : JsonCreationConverter<Input>
{
    protected override Input Create(Type objectType, JObject jObject)
    {
        var type = (string)jObject.Property("Type");
        switch (type)
        {
            case "grid":
                return new GridInput();
            case "text":
                return new TextInput();
            case "dropdown":
                return new DropdownInput();
            case "checkbox":
                return new CheckboxInput();
        }

        throw new ApplicationException(String.Format("The given type {0} is not supported!", type));
    }
}

I have implemented something so that the GridHeader is skiped (I dont need it to be deserialized, so it simply create an empty GridHeader during the deserializing process), but the RowList end up being always empty after the GridInput is deserialized. I have tried multiple things, but I am running out of idea...

TL:DR: The GridInput is created but the RowList is empty

Zwik
  • 646
  • 1
  • 9
  • 21
  • 2
    Try http://json2csharp.com/, copy your JSON and that will generate you a matching schema. Compare it with your current schema and see if there is a problem. – Habib Apr 22 '14 at 15:21
  • `this.RowList = new List();` Looks like you're making an empty `RowList`. – Timothy Shields Apr 22 '14 at 15:22
  • The json is actually generated from those Model, sent to the view, modified and sent back. All of that using Json.Net – Zwik Apr 22 '14 at 15:23
  • @Timothy, without this call, the Rowlist = null, so the problem persist – Zwik Apr 22 '14 at 15:23
  • @Zwik You're not showing enough code then. Serializing and deserializing some "plain old C#" classes with `List` members works perfectly fine on my end. – Timothy Shields Apr 22 '14 at 15:24
  • How is the Input class implemented? – Yuval Itzchakov Apr 22 '14 at 15:26
  • possible duplicate of [.NET Deserializing JSON to multiple types](http://stackoverflow.com/questions/12832306/net-deserializing-json-to-multiple-types) – Tim S. Apr 22 '14 at 15:35
  • @Yuval, I doubt this is the problem, but I will update my question, gimmy a couple of minutes – Zwik Apr 22 '14 at 15:41
  • @TimS. : No its not a duplicate of that question... – Zwik Apr 22 '14 at 15:49
  • @TimothyShields : I have updated the displayed code with the input and Step classes and my custom deserializer for the Abstract Input class – Zwik Apr 22 '14 at 15:52
  • @TimS. : Could you please remove the "might be a possible duplicate" thing you put on top of my question, as it is NOT a duplicate of that question and will mislead readers. – Zwik Apr 22 '14 at 16:00
  • @Zwik I'm not so sure that it's not related. It's not "on top of your question", just a comment and a link (and a sole close vote). For the moment, I'll leave it. If you can provide a short, but complete example (so far, none of yours have been [such an example](http://yoda.arachsys.com/csharp/complete.html)) demonstrating your problem and it's *not* related to that, I'd be glad to delete the comment. – Tim S. Apr 22 '14 at 16:12
  • @TimS. : the question you think is related is asking how to deserialze a list of abstract object, which I already do in my case and therefor know how to do. – Zwik Apr 22 '14 at 17:50

1 Answers1

1

I can't reproduce the problem. Is it possible that you forgot to have Json.NET use the InputJsonConverter class? The following code works.

void Main()
{
    var step = JsonConvert.DeserializeObject<Step>(s);
    Console.WriteLine(((GridInput)step.Input).RowList.Count); // 2
}
public class GridHeader { }
public class ActivityElement { public string Type { get; set; } }
public class Step : ActivityElement
{
    public Step()
    {
        this.Id = Guid.NewGuid();
        this.Label = "Basic Step";
        this.Type = "step";
    }

    public Guid Id { get; set; }
    public string Label { get; set; }
    public Input Input { get; set; }
}
[JsonConverter(typeof(InputJsonConverter))]
public abstract class Input
{
    public string Type { get; set; }
    public object Value { get; set; }
}

public class TextInput : Input
{
    public TextInput()
    {
        this.Type = "text";
    }
}
public class GridInput : Input
{
    public GridInput()
    {
        this.Type = "grid";
        this.GridHeader = new GridHeader();
        this.RowList = new List<GridRow>();
    }

    public GridHeader GridHeader { get; set; }
    public List<GridRow> RowList { get; set; }
}

public class GridRow
{
    public GridRow()
    {
        this.IsDeleted = false;
        this.CellList = new List<GridCell>();
    }

    public List<GridCell> CellList { get; set; }
    public bool IsDeleted { get; set; }
}

public class GridCell
{
    public GridCell()
    {
        this.name = "not set yet";
        this.Input = null;
    }

    public string name { get; set; }
    public Input Input { get; set; }
}
public abstract class JsonCreationConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject
        T target = Create(objectType, jObject);

        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }
    public override void WriteJson(
    JsonWriter writer,
    Object value,
    JsonSerializer serializer
)
    {
        throw new NotImplementedException();
    }
}
public class InputJsonConverter : JsonCreationConverter<Input>
{
    protected override Input Create(Type objectType, JObject jObject)
    {
        var type = (string)jObject.Property("Type");
        switch (type)
        {
            case "grid":
                return new GridInput();
            case "text":
                return new TextInput();
//            case "dropdown":
//                return new DropdownInput();
//            case "checkbox":
//                return new CheckboxInput();
        }

        throw new ApplicationException(String.Format("The given type {0} is not supported!", type));
    }
}
string s = @"{
    ""Id"":""e833ceae-57e5-4c52-8d56-b047790cb7c7"",
    ""Label"":""Grid time!"",
    ""Input"":
    {
        ""GridHeader"":{""ColumnDefinitionList"":[{""field"":""column1"",""title"":""Column 1""},{""field"":""column2"",""title"":""Column 2""},{""field"":""column3"",""title"":""Column 3""},{""field"":""column4"",""title"":""Column 4""}]},
        ""RowList"":
        [
            {
                ""CellList"":
                [
                    {""name"":""column1"",""Input"":{""Type"":""text"",""Value"":""cell 1 row 1""}},
                    {""name"":""column2"",""Input"":{""Type"":""text"",""Value"":""cell 2 row 1""}},
                    {""name"":""column3"",""Input"":{""Type"":""text"",""Value"":""cell 3 row 1""}},
                    {""name"":""column4"",""Input"":{""Type"":""text"",""Value"":""cell 4 row 1""}}
                ],
                ""IsDeleted"":false
            },
            {
                ""CellList"":
                [
                    {""name"":""column1"",""Input"":{""Type"":""text"",""Value"":""cell 1 row 2""}},
                    {""name"":""column2"",""Input"":{""Type"":""text"",""Value"":""cell 2 row 2""}},
                    {""name"":""column3"",""Input"":{""Type"":""text"",""Value"":""cell 3 row 2""}},
                    {""name"":""column4"",""Input"":{""Type"":""text"",""Value"":""cell 4 row 2""}}
                ],
                ""IsDeleted"":false
            }
        ],
        ""Type"":""grid"",
        ""Value"":null
    },
    ""DisplayPosition"":2,
    ""Type"":""step""
}";
Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • Na, if the InputJsonConverter isnt use, there is an abstract object deserialzing error that is raised. But the fact that you didnt reproduce it with this code makes me wonder a couple of things on my side, so I'll check and see. Thank you tim! – Zwik Apr 22 '14 at 17:52
  • Ok I found the culprit, it was the deserialization of GridHeader that was somehow canceling the deserialization of the RowList... Found it because of your test, thank you very much Tim :) – Zwik Apr 22 '14 at 17:57