12

I created a class PagedResult<T> : List<T> that contains a few added members in order to work with one of our components. However, when I run json deserializer, it only serializes the list. If I markup the derived class with [JsonObject] and [JsonProperty] then it'll only serialize the members of the derived class and not the list. How do I get both?

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
Sinaesthetic
  • 11,426
  • 28
  • 107
  • 176

3 Answers3

12

By default, Json.Net will treat any class that implements IEnumerable as an array. You can override this behavior by decorating the class with a [JsonObject] attribute, but then only the object properties will get serialized, as you have seen. The list itself will not get serialized because it is not exposed via a public property (rather, it is exposed via the GetEnumerator() method).

If you want both, you can either do as @Konrad has suggested and provide a public property on your derived class to expose the list, or you can write a custom JsonConverter to serialize the whole thing as you see fit. An example of the latter approach follows.

Assuming that your PagedResult<T> class looks something like this:

class PagedResult<T> : List<T>
{
    public int PageSize { get; set; }
    public int PageIndex { get; set; }
    public int TotalItems { get; set; }
    public int TotalPages { get; set; }
}

You can make a converter for it like this:

class PagedResultConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(PagedResult<T>));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        PagedResult<T> result = (PagedResult<T>)value;
        JObject jo = new JObject();
        jo.Add("PageSize", result.PageSize);
        jo.Add("PageIndex", result.PageIndex);
        jo.Add("TotalItems", result.TotalItems);
        jo.Add("TotalPages", result.TotalPages);
        jo.Add("Items", JArray.FromObject(result.ToArray(), serializer));
        jo.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        PagedResult<T> result = new PagedResult<T>();
        result.PageSize = (int)jo["PageSize"];
        result.PageIndex = (int)jo["PageIndex"];
        result.TotalItems = (int)jo["TotalItems"];
        result.TotalPages = (int)jo["TotalPages"];
        result.AddRange(jo["Items"].ToObject<T[]>(serializer));
        return result;
    }
}

(Notice also that the [JsonObject] and [JsonProperty] attributes are not required with this approach, because the knowledge of what to serialize is encapsulated into the converter class.)

Here is a demo showing the converter in action:

class Program
{
    static void Main(string[] args)
    {
        PagedResult<string> result = new PagedResult<string> { "foo", "bar", "baz" };
        result.PageIndex = 0;
        result.PageSize = 10;
        result.TotalItems = 3;
        result.TotalPages = 1;

        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.Converters.Add(new PagedResultConverter<string>());
        settings.Formatting = Formatting.Indented;

        string json = JsonConvert.SerializeObject(result, settings);
        Console.WriteLine(json);
    }
}

Output:

{
  "PageSize": 10,
  "PageIndex": 0,
  "TotalItems": 3,
  "TotalPages": 1,
  "Items": [
    "foo",
    "bar",
    "baz"
  ]
}
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • "Json.Net will treat any class that implements `IEnumerable` as an array. You can override this behavior by decorating the class with a `[JsonObject]` attribute, but then only the object properties will get serialized" My experience is slightly different: the OP derived from `List`, whereas I have implemented `IList`, once in a base class, once in the serialized object directly to get my implementation of `IEnumerable`. In both cases, I get the added properties and collection objects as soon as I decorate with `[JsonObject]`. Could be down to a newer version of Json.NET (12.0.0). – Bob Sammers Nov 30 '18 at 16:17
3

The simplest workaround that comes to my mind is to expose internal elements as another property of derived class:

[JsonProperty]
public IEnumerable<T> Elements {
    get 
    { 
       return this; 
    }
}
Konrad Kokosa
  • 16,563
  • 2
  • 36
  • 58
0

If you don't want to write custom JsonConverter or use JSON attributes (JsonObjectAttribute) then you could simply use extension methods:

class PagedResult<T> : List<T>
{
    public int PageSize { get; set; }
    public int PageIndex { get; set; }
    public int TotalItems { get; set; }
    public int TotalPages { get; set; }
}

public static class Extensions
{
    public static string ToPagedJson<T>(this PagedResult<T> pagedResult)
    {
         return JsonConvert.SerializeObject(new
         {
             PageSize = pagedResult.PageSize,
             PageIndex = pagedResult.PageIndex,
             TotalItems = pagedResult.TotalItems,
             TotalPages = pagedResult.TotalPages
             Items = pagedResult
         });
    }
}
41D0
  • 161
  • 5