2

I have the following code which represents the results of a paged query.

public class Page<T> : IEnumerable<T>
{
    public IEnumerable<T> Source { get; }
    public int PageNumber { get; }
    public int PageSize { get; }
    public int TotalCount { get; }

    public int TotalPages => TotalCount / PageSize + (TotalCount % PageSize > 0 ? 1 : 0);

    public Page(
        IEnumerable<T> source,
        int pageNumber,
        int pageSize,
        int totalCount)
    {
        Source = source;
        PageNumber = pageNumber;
        PageSize = pageSize;
        TotalCount = totalCount;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return Source.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

And here is an MVC controller method that returns a page of results via the MVC Json Serializer (not the WebAPI serializer).

    [HttpGet, Route("mergeable")]
    public async Task<ActionResult> Mergeable([FromUri] Mergeable.Query query)
    {
        Page<Mergeable.Model> result = await mediator.Send(query);

        return Json(result, JsonRequestBehavior.AllowGet);
    }

When called, the response Json is the array of T. All the paging meta properties (TotalCount, PageNumber, PageSize) are not serialized.

Does the default MVC Json serializer (whatever that is) ignore other properties on an instance of IEnumerable? How can I override this behaviour?

Matt
  • 1,648
  • 12
  • 22
  • 2
    Please explain how you would like the object to be serialized, you can't have something that is *both* an object and an array in JSON, it's either or. An array cannot have properties, and an object cannot have free elements. As such **your question makes no sense**. You should change your objects to have one object with the properties you want and that contains the collection in a property, instead of *being* the collection. – Lasse V. Karlsen Mar 09 '18 at 08:48
  • I would have expected the resulting json would contain the meta properties as top-level elements, and the array in a property called Source, as per the serialization source object. The fact that the object implements IEnumerable precludes all the other properties seems surprising, doesn't it? – Matt Mar 09 '18 at 10:40
  • 1
    No, it doesn't seem surprising to me because I've read the docs. The [Json](https://msdn.microsoft.com/en-us/library/system.web.mvc.controller.json(v=vs.118).aspx) method uses [JavaScriptSerializer](https://learn.microsoft.com/en-us/dotnet/api/system.web.script.serialization.javascriptserializer?view=netframework-4.7.1) to serialize the object, which has this information: "Types that implement IEnumerable or System.Collections.Generic.IEnumerable ...: Array that uses JSON array syntax" – Lasse V. Karlsen Mar 09 '18 at 10:48
  • What is surprising is a Page *singular* implementing IEnumerable. I would expect a Page with an Items property. In any case, all containers implement IEnumerable and *all* of them have additional properties and methods. Serializing all of them as arrays is the most common case – Panagiotis Kanavos Mar 09 '18 at 13:21

2 Answers2

1

ASP.NET Web API and ASP.NET Core MVC (which also runs on the full framework) use Json.NET. Json.NET will also generate an array from an object that implements IEnumerable.

This code will produce [1,2,3]

var page=new Page<int>(new int[]{1,2,3},1,10,100);
var text=Newtonsoft.Json.JsonConvert.SerializeObject(page);

Json.NET allows you to specify how you want the object to be serialized through attributes like JsonObject or JsonArray. If you add the JsonObject attribute to the Page class it will be serialized to :

{"Source":[1,2,3],"PageNumber":1,"PageSize":10,"TotalCount":100,"TotalPages":10}

ASP.NET MVC still uses the rather old JavaScriptSerializer whose description is :

Json.NET should be used serialization and deserialization. Provides serialization and deserialization functionality for AJAX-enabled applications.

You can configure ASP.NET MVC to use Json.NET as shown in this SO question. If you don't want to do that, you could use Json.NET to serialize to a string and return the string with a JSON content type, eg :

 return Content(JsonConvert.SerializeObject(page), "application/json");
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
0

It is a Json format limitation, the json format do not support comlex arrays with additional properties.

The json serialization behavior for objects implemeting IEnumerable is to ignore other properties and just serialize from GetEnumerator().

You can use the Source property to get your elements and avoid the Page<T> : IEnumerable<T> implementation.

animalito maquina
  • 2,264
  • 3
  • 20
  • 35