Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON serialize properties on class inheriting list [duplicate]

I have a model as follows:

public class TestResultModel
{
    public bool Successful { get; set; }
    public string ErrorMessage { get; set; }
}

public class TestResultListModel : List<TestResultModel>
{
    public int TotalTestCases { get { return base.Count; } }

    public int TotalSuccessful { get { return base.FindAll(t => t.Successful).Count; } }
}

I return this TestResultListModel from an ApiController:

var testResultListModel = new TestResultListModel();
foreach (var testCaseId in new int[] {1,2,3,4})
{
    var testResultModel = new TestResultModel
    {
        Successful = true,
        ErrorMessage = "STRING"
    };

    testResultListModel.Add(testResultModel);
}
return testResultListModel;

When I inspect the JSON result it does contain all the TestResultModels, but the properties on the TestResultListModel (TotalTestCases and TotalSuccesful) are not visible.

How can I also include these values in the JSON-serialized object?

What I tried is using JSON.NET and decorating the properties with the attribute [JsonProperty], this was unsuccessful.

like image 640
Erwin Rooijakkers Avatar asked Feb 16 '16 17:02

Erwin Rooijakkers


1 Answers

Your basic difficulty here is that JSON has two types of container: an object, and an array. From the standard:

  • An array is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma).

  • An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace).

To force a collection's properties to be serialized, mark it with [JsonObject]:

[JsonObject]
public class TestResultListModel : List<TestResultModel>
{
    public int TotalTestCases { get { return base.Count; } }

    public int TotalSuccessful { get { return base.FindAll(t => t.Successful).Count; } }
}

Of course, if you do this, the items will not be serialized, because a JSON container can have properties, or items -- but not both. If you want both, you will need to add a synthetic array property to hold the items -- which can be private if you want.

[JsonObject] will also cause base class properties such as Capacity to be serialized, which you likely do not want. To suppress base class properties, use MemberSerialization.OptIn. Thus your final class should look something like:

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class TestResultListModel : List<TestResultModel>
{
    [JsonProperty]
    public int TotalTestCases { get { return base.Count; } }

    [JsonProperty]
    // Using Enumerable.Count() is more memory efficient than List.FindAll()
    public int TotalSuccessful { get { return this.Count(t => t.Successful); } }

    [JsonProperty]
    TestResultModel[] Items
    {
        get
        {
            return this.ToArray();
        }
        set
        {
            if (value != null)
                this.AddRange(value);
        }
    }
}

This gives JSON that looks like:

{
  "TotalTestCases": 4,
  "TotalSuccessful": 2,
  "Items": [
    {
      "Successful": false,
      "ErrorMessage": "STRING"
    },
    {
      "Successful": true,
      "ErrorMessage": "STRING"
    },
    {
      "Successful": false,
      "ErrorMessage": "STRING"
    },
    {
      "Successful": true,
      "ErrorMessage": "STRING"
    }
  ]
}

It is possibly more work than it's worth, since these properties could be reconstructed easily on the client side. (The question Why not inherit from List? suggests avoiding this sort of design.)

like image 162
dbc Avatar answered Nov 17 '22 04:11

dbc