0

I have a JSON object that is coming back based on the keyword that the user type. It is returning only some of the typed keyword from the user. When I check postman and type the same keyword it returns properly.

The following exception is raised:

Newtonsoft.Json.JsonSerializationException: 
Must specify valid information for parsing in the string

JSON

{
  "valid": true,
  "result": {
    "points": [
      {
        "pointId": "505",
        "name": "Building one",
        "description": "Office of Technology and Data Application Development",
        "latitude": "xxx",
        "longitude": "xxx",
        "floor": "B",
        "aliases": [],
        "comments": [],
        "images": []
      }
    ],
    "categories": []
  },
  "errors": []
}

Model classes

public class SearchPoints
{
    public bool Valid { get; set; }
    public Result Result { get; set; }
    public List<string> Errors { get; set; }
}

public class Result
{
    public List<Point> Points { get; set; }
    public List<Category> Categories { get; set; }
}

public class Category
{
  public long CategoryId { get; set; }
  public string Name { get; set; }
  public long ParentId { get; set; }
  public string Parent { get; set; }
}

public class Point
{
    public long PointId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string Latitude { get; set; }
    public string Longitude { get; set; }
    public Floor Floor { get; set; }
    public List<string> Aliases { get; set; }
    public List<string> Comments { get; set; }
    public List<string> Images { get; set; }
}

public enum Floor 
{ 
    A, 
    B 
};

Service method

var apiResponse = await _httpClient.GetAsync(Url + _keyword);
var apiContent = apiResponse.Content.ReadAsStringAsync().Result;
var pointsJsonResponse = JsonConvert.DeserializeObject<SearchPoints>(apiContent);

var potentialPoints = new ObservableCollection<Point>(pointsJsonResponse.Result.Points);

PointsItemSource = potentialPoints;
dbc
  • 104,963
  • 20
  • 228
  • 340
Marc-Anthony
  • 102
  • 9
  • have you compared the raw json returned in code vs. postman? Are they the same or different? If they are different then none of the code you posted is relevant to solving the problem. – Jason Aug 14 '19 at 16:07
  • Yes i compared them and they are the same. – Marc-Anthony Aug 14 '19 at 16:13
  • so when you deserialize there are just some items missing? How many? Is there any pattern? – Jason Aug 14 '19 at 16:15
  • @Anthony How are you sending up the typed keyword? As the user types or do they have a submit button to click? – Gary Stewart Aug 14 '19 at 16:16
  • Their is no number to how many is missing, i dont think there is a pattern. How i know some are missing is when i test the same keyword in postman i get the correct listing but when i test with the app i dont – Marc-Anthony Aug 14 '19 at 16:17
  • @GaryStewart as the user types i am sending up the keyword and hitting the API – Marc-Anthony Aug 14 '19 at 16:18
  • Do you think it has something to do with the TextChanged event ?? – Marc-Anthony Aug 14 '19 at 16:19
  • So what you are seeing are the results as the user types. The requests are going up in an async manner and can return out of order. – Gary Stewart Aug 14 '19 at 16:19
  • @GaryStewart yes as the user types i am returning results from the API – Marc-Anthony Aug 14 '19 at 16:21
  • @Anthony Exactly ... You are probably firing multiple calls to the API before the previous call(s) have returned. – Gary Stewart Aug 14 '19 at 16:25
  • @GaryStewart is there a way to limit that?? – Marc-Anthony Aug 14 '19 at 16:26
  • @GaryStewart i logged the error to App center as it returns `Newtonsoft.Json.JsonSerializationException: Must specify valid information for parsing in the string` – Marc-Anthony Aug 14 '19 at 16:36
  • Your answers are contradicting each other. You said the raw responses from Postman and the app are identical. Then later you say one returns a correct result and one doesn't - so obviously they aren't identical. – Jason Aug 14 '19 at 16:52
  • @Jason it is identical, when i debug it skip this line: `var potentialPoints = new ObservableCollection(pointsJsonResponse.Result.Points);` – Marc-Anthony Aug 14 '19 at 16:57
  • The JSON in your question deserializes successfully, see https://dotnetfiddle.net/nDhNPL – dbc Aug 14 '19 at 21:52

2 Answers2

2

The JSON shown in your question deserializes successfully into the data model shown in your question, see https://dotnetfiddle.net/nDhNPL.

The exception shown in your question, however, provides some information. The (somewhat opaque) message Must specify valid information for parsing in the string only appears only once in the Json.NET sources, according to a search, specifically in EnumUtils.ParseEnum(Type enumType, NamingStrategy? namingStrategy, string value, bool disallowNumber):

public static object ParseEnum(Type enumType, NamingStrategy? namingStrategy, string value, bool disallowNumber)
{

// SNIP

    if (firstNonWhitespaceIndex == -1)
    {
        throw new ArgumentException("Must specify valid information for parsing in the string.");
    }

The relevant code is throwing an exception if, when parsing an enum string, the incoming value contains only whitespace. So I modified the JSON in your question as follows:

    "floor": " ",

And reproduced the exception as shown here: https://dotnetfiddle.net/6zaG9W.

Thus you must actually be attempting to parse JSON with an empty or whitespace value for Floor and failing because this is not possible. To make this work, you could:

  1. Change Point.Floor to be a string:

    public string Floor { get; set; }
    

    The JSON will now parse successfully. Demo fiddle here: https://dotnetfiddle.net/zg5YWr.

  2. Use a special subclass of StringEnumConverter that handles empty strings for Floor. One example is shown in the answer to How to handle deserialization of empty string into enum in json.net by Davor Zlotrg, which I generalize here:

    public class EmptyTolerantStringEnumConverter : StringEnumConverter
    {
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.String && string.IsNullOrWhiteSpace(reader.Value.ToString()))
                // Return a default value.  Note if deserializing a nullable enum the default value is null
                return Activator.CreateInstance(objectType);
    
            return base.ReadJson(reader, objectType, existingValue, serializer);
        }       
    }
    

    And apply it as follows:

    [JsonConverter(typeof(EmptyTolerantStringEnumConverter))]
    public Floor Floor { get; set; }
    

    Or, if you prefer to map "" to a nullable:

    [JsonConverter(typeof(EmptyTolerantStringEnumConverter))]
    public Floor? Floor { get; set; }
    

    Demo fiddle here: https://dotnetfiddle.net/DwGKHU

Related: How to tell Json.Net globally to apply the StringEnumConverter to all enums and parsing an enumeration in JSON.net.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    Thank you, i knew i was parsing the JSON correctly but i didnt understand `EnumUtils.ParseEnum (System.Type enumType, Newtonsoft.Json.Serialization.NamingStrategy namingStrategy, System.String value, System.Boolean disallowNumber)` error – Marc-Anthony Aug 14 '19 at 22:53
0

I'm pretty sure Json.Net cannot deserialize an 'object' type. It needs the real type:

So replace all your List<object> with List<TRealType>.

Example:

    public List<string> Aliases { get; set; }
    public List<string> Comments { get; set; }
    public List<string> Images { get; set; }

If you still get the error, then the error is in your apiContent variable which would not be a proper json string.

You also need to put the [JsonConverter(typeof(StringEnumConverter))] attribute on your enum type.

Roubachof
  • 3,351
  • 3
  • 23
  • 34
  • It still doesnt seem to deserialize – Marc-Anthony Aug 14 '19 at 18:05
  • Then your issue is in your `apiContent` string, so you need to update your post and add the code which creates this string. – Roubachof Aug 14 '19 at 18:09
  • Well your update is not good: you retrieve a apiResponse, and you deserialize a apiContent. – Roubachof Aug 14 '19 at 18:25
  • ok now put a breakpoint just after this line, and copy paste the value of apiContent in your post. And also update your post with all your SearchPoints, Result and Point classes with the changes you made on the List – Roubachof Aug 14 '19 at 18:33
  • the casing of your property names does not match the json – Jason Aug 14 '19 at 19:01
  • your property is "Valid" and the json is "valid" - Newtonsoft will not (I think) match those unless you specify a mapping attribute. The same problem applies to all of the properties in your model. – Jason Aug 14 '19 at 19:07
  • This is what makes it tricky because it did pick up that keyword and showed it on the screen. To be clear it rendered that above JSON and showed it in a listview correctly – Marc-Anthony Aug 14 '19 at 19:10
  • @Anthony update your initial post, please stop adding comments. Also update SearchPoints, Result and Point classes with the changes you made on the List – Roubachof Aug 14 '19 at 19:13
  • you didn't update your Model classes.. There are still some List in it – Roubachof Aug 14 '19 at 19:38