Deserialize JSON into custom list

2019-07-14 01:35发布

问题:

I have this json:

var x = [
    [99,"abc","2dp",{"GroupNum": 0,"Total":[4, 1]}],
    [7,"x","date"],
    [60,"x","1dp",{"GroupNum": 1}],
    ...
]

The following rules exist (let i refer to the inner list index):

  • x[i][0] - mandatory item - always an integer
  • x[i][1] - mandatory item - always a string
  • x[i][2] - mandatory item - always a string
  • x[i][3] - optional item - when it exists then it has the following rules:
    • x[i][3].GroupNum - mandatory field - always an integer
    • x[i][3].Total - optional field - when it exists then it is a list of integers

So I have created the following classes for these rules:

public class ReportTemplateField : System.Collections.CollectionBase
{
    public object this[int index]
    {
        get
        {
            switch (index)
            {
                case 0:
                    return (int)List[index];
                case 1:
                case 2:
                    return (string)List[index];
                case 3:
                    return (ReportGrouping)List[index];
                default:
                    throw new System.ArgumentOutOfRangeException("Class ReportTemplateField only contains 4 items");
            }
        }
        set
        {
            switch (Count)
            {
                case 0:
                    List[index] = unchecked((int)value);
                    break;
                case 1:
                case 2:
                    List[index] = (string)value;
                    break;
                case 3:
                    List[index] = (ReportGrouping)value;
                    break;
                default:
                    throw new System.ArgumentOutOfRangeException("Class ReportTemplateField may only contain 4 items");
            }
        }
    }

    public void Add(object item)
    {
        switch (Count)
        {
            case 0:
                List.Add(unchecked((int)item));
                break;
            case 1:
            case 2:
                List.Add((string)item);
                break;
            case 3:
                List.Add((ReportGrouping)item);
                break;
            default:
                throw new System.ArgumentOutOfRangeException("Class ReportTemplateField may only contain 4 items");
        }
    }
}

public class ReportGrouping
{
    public int GroupNum { get; set; }
    public List<int> Total { get; set; }
}

However, when I attempt to deserialize using json.net, the ReportTemplateField class never gets used. I know this because I have put breakpoints in every method and none of them get triggered:

string json = "[[99,\"abc\",\"2dp\",{\"GroupNum\": 0,\"Total\":[4, 1]}],[7,\"x\",\"date\"],[60,\"x\",\"1dp\",{\"GroupNum\": 1}]]";
List<ReportTemplateField> parsed = JsonConvert.DeserializeObject<List<ReportTemplateField>>(json);
int Total01 = parsed[0][3].Total[1];

gives me this error:

error CS1061: 'object' does not contain a definition for 'Total' and no extension method 'Total' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)

However it works if I just deserialize the ReportGrouping in isolation:

string json2 = "{\"GroupNum\": 0,\"Total\":[4, 1]}";
ReportGrouping parsed2 = JsonConvert.DeserializeObject<ReportGrouping>(json2);
parsed2.Total[1]; // 1

So the problem lies with the ReportTemplateField class. Maybe I shouldn't be inheriting from System.Collections.CollectionBase? I basically followed this walkthough with some modifications - https://msdn.microsoft.com/en-us/library/xth2y6ft(v=vs.71).aspx but it does say that the documentation may be outdated. I'm using .NET 4.5 so please factor that into any your answers.

How can I get Json.net to deserialize correctly, so that I can just go parsed[0][3].Total[1]; to access the integer value of the second total without needing to cast it or do anything to it at all?

回答1:

There is a few errors in the code, 1 is that object does not have a property called Total and another is that parsed[0][3] is a string.

You have a few errors in the code:

  1. parsed[0][3] is a string. Fix it by converting the string into a ReportGrouping.
  2. new ReportGrouping().GroupName should be named Group according to Json.
  3. After you have parsed parsed[0][3] into a ReportingGroup it will still return a object, so cast i into a ReportingGroup afterwards. I.e. (ReportingGroup)parsed[0][3].

    get
    {
        switch (index)
        {
            case 0:
                return (int)List[index];
            case 1:
            case 2:
                return (string)List[index];
            case 3:
                return JsonConvert.DeserializeObject<ReportGrouping>(List[index].ToString());
            default:
                throw new System.ArgumentOutOfRangeException("Class ReportTemplateField only contains 4 items");
        }
    }
    

And use it like this:

List<ReportTemplateField> parsed = JsonConvert.DeserializeObject<List<ReportTemplateField>>(json);
ReportGrouping group = (ReportGrouping)parsed[0][3];

Dont forget to rename GroupName into Group and to fix the setter.

EDIT: Sorry for repeating myself so many times..