Search code examples
c#jsonjson-deserialization

How to deserialize json with nest array of dynamic keys in C#


I have the following json structure from an api return:

{
  "firstName": "test first",
  "lastName": "test last",
  "moduleReports": [
    {
      "completion": "Nothing shared",
      "thoughts": "example",
      "componentTitle": "CareerCorner"
    },
    {
      "thoughts": "example",
      "componentTitle": "CareerLIbrary"
    },
    {
      "completionDate": "0001-01-01T00:00:00",
      "componentTitle": "Discoverer"
    },
    {
      "completionDate": "0001-01-01T00:00:00",
      "componentTitle": "Explorer"
    },
    {
      "workValues": [
        "independence",
        "relationships"
      ],
      "componentTitle": "Navigator"
    },
    {
      "personalityTypes": [
        "Creator",
        "Doer"
      ],
      "componentTitle": "Pathfinder"
    },
    {
      "reflection": "example",
      "completionDate": "0001-01-01T00:00:00",
      "componentTitle": "QuickPic"
    },
    {
      "careerGroup": "Ideas",
      "componentTitle": "Scout"
    },
    {
      "improveAtSkills": [
        "listen",
        "speak"
      ],
      "goodAtSkills": [
        "decision",
        "solve"
      ],
      "componentTitle": "Trekker"
    }
  ]
}

I tried to create the classes myself and came up with

public class ModuleReport
  {
    public string completion { get; set; }
    public string thoughts { get; set; }
    public string componentTitle { get; set; }
    public DateTime? completionDate { get; set; }
    public List<string> workValues { get; set; }
    public List<string> personalityTypes { get; set; }
    public string reflection { get; set; }
    public string careerGroup { get; set; }
    public List<string> improveAtSkills { get; set; }
    public List<string> goodAtSkills { get; set; }
  }

  public class ModuleResult
  {
    public string firstName { get; set; }
    public string lastName { get; set; }
    public List<ModuleReport> moduleReports { get; set; }
  }

but when I call

var mResults = JsonConvert.DeserializeObject<List<ModuleResult>>( jsonString );

in the controller I get the following error

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[...Reports.User.ModuleResults]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.

I have tried following the advice found in the answers here: How to deserialize JSON with dynamic and static key names in C# and hereSerialize/Deserialize dynamic property name using JSON.NET but I keep getting the same error.

I started to build the following

public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
    {
      var responseObject = JObject.Load( reader );

      ModuleResult response = new ModuleResult
      {
        firstName = (string)responseObject[ "firstName" ],
        lastName = (string)responseObject[ "lastName" ],
      };

      var varData = new Dictionary<string, object>();

      foreach ( var property in responseObject.Properties() )
      {
        if ( property.Name == "firstName" || property.Name == "lastName" )
        {
          continue;
        }

        varData.Add( property.Name, property.Value );
      }

      response.VarData = varData;
      return response;
    }

but when I put it into place I realized that it would just add the entire "moduleResults" node as key and all it's contents as the value. So back to square one.

I know the issue is accessing the "moduleReports" json array and properly deserializing those key/value pairs, but I'm having a hell of a time figuring out how to dig in to that nested array and then how to deal with the changing key/value pairs.


Solution

  • Why do you want to deserialize it as List<ModuleResult>, when the JSON structure you've provided indicates that it is a single ModuleResult object?

    Just change

    var mResults = JsonConvert.DeserializeObject<List<ModuleResult>>( jsonString ); 
    

    To

    var mResults = JsonConvert.DeserializeObject<ModuleResult>( jsonString ); 
    

    and it will be deserialized correctly.