Newtonsoft JSON Deserialization Issue



  • So, I have the following C# code as an example:

    [JsonObject] public class SSOErrorResponse {
    	[JsonProperty("$id")] public string ID { get; set; }
    	[JsonProperty("$values")] public List<SSOError> Errors { get; set; }
    }
    [JsonObject] public class SSOError {
    	[JsonProperty("$id")] public string ID { get; set; }
    	[JsonProperty] public string Title { get; set; }
    	[JsonProperty] public string Message { get; set; }
    	[JsonProperty] public string FullException { get; set; }
    }
    

    And this is the code that makes, serializes, and tries to deserialize those objects to JSON:

    var test = new SSOErrorResponse() { ID = "1", Errors = new List<SSOError>() };
    test.Errors.Add(new SSOError() { ID = "2", Message = "Test", FullException = "Message: Test" });
    var result = JsonConvert.SerializeObject(test) { Formatting = Newtonsoft.Json.Formatting.Indented });
    test = JsonConvert.DeserializeObject<SSOErrorResponse>(result);
    

    On line 3, which serializes the object to JSON, it generates this:

    {
      "$id": "1",
      "$values": [
        {
          "$id": "2",
          "Title": null,
          "Message": "Test",
          "FullException": "Message: Test"
        }
      ]
    }
    

    But then, line 4 fails with this exception:

    JsonSerializationException: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'SSOErrorResponse' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.

    To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.

    Path '$values', line 3, position 14.

    So... how do I fix the object(s) so this library can deserialize the very JSON it was able to generate?



  • @ChaosTheEternal I found this site:

    Given what your JSON looks like, these are the proposed class structures:

         public class Value
        {
            [JsonProperty("$id")]
            public string $id { get; set; }
    
            [JsonProperty("Title")]
            public object Title { get; set; }
    
            [JsonProperty("Message")]
            public string Message { get; set; }
    
            [JsonProperty("FullException")]
            public string FullException { get; set; }
        }
    
        public class SSOErrorResponse
        {
            [JsonProperty("$id")]
            public string $id { get; set; }
    
            [JsonProperty("$values")]
            public IList<Value> $values { get; set; }
        }
    

    Only difference I'm seeing is the use of IList - maybe that's the culprit?

    The example by Microsoft also uses IList



  • @Rhywden No good. Exact same exception whether I use List, IList, or an actual array.

    Worse is, the response JSON I can't change, as it actually comes from a vendor. I'm just able to replicate generating it with that object.


  • I survived the hour long Uno hand

    @ChaosTheEternal
    Out of curiosity, what happens if you test with serializing and deserializing when all errors have all their properties set (e.g. no NULLs)? ISTR we struggled with JSON deserialization translating NULLs, but that was a while ago and my CRS has blocked exactly what data types caused a fit.



  • @izzion Still blows up, exact same exception.



  • OK, so, plus side, my object design is fine. In my test code, if I remove the dollar sign from the property title in the JsonProperty(), it works.

    The issue appears to be related to the properties starting with a dollar sign, which are apparently designated as "metadata" and not normal fields.

    Now... how can I actually get it to read those as if they aren't metadata fields but just named like them? Again, we're not generating the JSON like this, just consuming it.


  • I survived the hour long Uno hand

    @ChaosTheEternal
    Are there other metadata fields that you want to ignore? Could you just "clean" the incoming data by stripping "$ down to " before throwing it into the deserializer?

    Kind of the :kneeling_warthog: approach instead of the :technically-correct: one, but it's not like we get paid by LOC, right?



  • @ChaosTheEternal It works if you instead use JsonPropertyName:

    public class SSOError
        {
            [JsonPropertyName("$id")]
            public string Id { get; set; }
    
            [JsonPropertyName("Title")]
            public string Title { get; set; }
    
            [JsonPropertyName("Message")]
            public string Message { get; set; }
    
            [JsonPropertyName("FullException")]
            public string FullException { get; set; }
        }
    
        public class SSOErrorResponse
        {
            [JsonPropertyName("$id")]
            public string Id { get; set; }
    
            [JsonPropertyName("$values")]
            public List<SSOError> Errors { get; set; }
        }
    


  • @Rhywden JsonPropertyName is not part of Newtonsoft.Json, from what I can see.



  • @ChaosTheEternal .NET Core I fear, System.Text.Json.Serialization



  • @izzion I might just have to go this route, though I don't like it.


  • I survived the hour long Uno hand



  • @izzion I did, but from what I found in their documentation is that it's meant to make it so the JSON doesn't need to have that stuff as the first properties at each level to be recognized.

    I think I could make it work if I set up a contract resolver, but I'll worry about that later. The workaround of switching "$ to " in what we receive will work for now.


  • Discourse touched me in a no-no place

    @ChaosTheEternal said in Newtonsoft JSON Deserialization Issue:

    The workaround of switching "$ to " in what we receive will work for now.

    Preparing the front-page article for 3 years from now…



  • @ChaosTheEternal Yeah I was going to suggest Deserialise(JSON.replace("$id", "id"). Although maybe you should replace it with "_dollar_id" so it is clear something has happened, otherwise someone will not notice and there'll be a front page post about debugging this in 5 years' time.


Log in to reply