0

In a Web API controller, I return a JSON encoded object, in fact a list of Organization objects, each with its own collection of Branch objects, again each with a list of Person objects.

I return it like this from the controller, as an IHttpActionResult:

return Ok(SerializeObject(orgs));

where:

protected virtual string SerializeObject(object source)
{
    return JsonConvert.SerializeObject(source, SharedSettings.JsonSerializerSettings);
}

and

public static JsonSerializerSettings JsonSerializerSettings { get; } = new JsonSerializerSettings
{
    Formatting = Formatting.Indented,
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    StringEscapeHandling = StringEscapeHandling.EscapeNonAscii,
    TypeNameHandling = TypeNameHandling.None,
    NullValueHandling = NullValueHandling.Include
};

I request it like:

HttpResponseMessage response = await Client.PostAsync(resource, null);
if (!response.IsSuccessStatusCode)
{
    RaiseException(response, exMsg);
}
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TReturn>(content, SharedSettings.JsonSerializerSettings);

I don't use response.Content.ReadAsAsync<TReturn>() because I want to be really sure I'm using the same Json serializer and its settings on both sides. The JSON returned looks fine, if you'll excuse a few lines for brevity:

[
    {
        "Branches": [
            {
                "People": [
                    {
                        "FullName": "Katie Darwin",
                        "BranchId": 2,
                        "OrgId": 2,
                        "Id": 18
                    }
                ],
                "Name": "Jolimont",
                "Phone": null,
                "OrgId": 2,
                "Id": 2
            }
        ],
        "Name": "Contoso, Ltd.",
        "Id": 2
    },
    {
        "Branches": [],
        "Name": "Trey Research",
        "Id": 11
    }
]

The exception I get on deserialisation is

'Error converting value "" " to type 'System.Collections.Generic.List`1[ApptEase.DataModels.Entities.Organization]'. Path '', line 1, position 439200.

I have never experienced any such trouble with NewtonSoft JSON before, but maybe I'm only placed less demand on it. Does anyone have any idea what could be wrong?

BREAKING: If I write that JSON string to a file, then read it with a small test program, it deserialises fine, with no explicit options. Being hopeful, I removed the options clause from the deserialize in the main program, but I still get the same error. I think I should start looking at a text encoding problem.

It might also be noted that the Content-Length header and the error position are equal: 439200.

ProfK
  • 49,207
  • 121
  • 399
  • 775
  • What's at position 439200? Can you post the chunk? – JuanR Nov 15 '16 at 03:58
  • @Juan a quick `json[439200]` check reveals a space, i.e. char 32, and that would seem to be a space after the overall ending ']'. – ProfK Nov 15 '16 at 04:27
  • The error seems to suggest there is a problem with the JSON. What are you using to generate it? Here is another suggestion: get a JSON visualizer plugin for your browser and hit the service directly if possible. See if the parser cries. – JuanR Nov 15 '16 at 04:46
  • You must be double-serializing your object on the server side along the lines of [Turn C# object to json string, how to handle double quotation](https://stackoverflow.com/questions/22293208) and [JSON.NET Parser *seems* to be double serializing my objects](https://stackoverflow.com/questions/25559179/). [`OK()`](https://msdn.microsoft.com/en-us/library/system.web.http.apicontroller.ok(v=vs.118).aspx#M:System.Web.Http.ApiController.Ok%60%601%28%60%600%29) should serialize for you. – dbc Nov 15 '16 at 04:47
  • @Juan, the parser doesn't cry, but only because the content returned is all double serialised JSON, i.e. all non-alpha-num elements are escaped. I'm not using anything to generate it, I leave that up to the `ApiController`, e.g. `return Ok(orgs)` where `orgs` is `List`.. – ProfK Nov 15 '16 at 05:20

1 Answers1

0

I was stupid, and stupid to let myself be misled by so many varied and so incorrect examples. I had forgotten that one of the tenets of an ApiController is that it act more like a repository than a controller, with no HTTP crap anywhere. The controller in question now looks like this:

[System.Web.Mvc.RoutePrefix("api/Org")]
public class OrgController : BaseController
{
    [HttpPost]
    public IEnumerable<Organization> Get()
    {
        Db.Configuration.LazyLoadingEnabled = false;
        return Db.Organizations.ToList();
    }
}

And the object returned is obtained like this, with the Content.ReadAsAsync<TReturn>().

public virtual async Task<TReturn> PostAsync<TReturn>(string resource)
{
    var exMsg = $"Post request failed to get resource: '{resource}' from '{Client.BaseAddress}'.";
    try
    {
        HttpResponseMessage response = await Client.PostAsync(resource, null);
        if (!response.IsSuccessStatusCode)
        {
            RaiseException(response, exMsg);
        }
        return await response.Content.ReadAsAsync<TReturn>();
    }
    catch (Exception ex) when (ex.GetType() != typeof(RestClientException))
    {
        throw;
    }
}

I probably should be using custom Response objects, that can provide the caller more information, e.g. an exception caught, but raw like this is how it was designed to work and work like this it does.

ProfK
  • 49,207
  • 121
  • 399
  • 775