Home > Enterprise >  How can I deserialize complex json repsonse into easy-to-deserialize structure using Newtonsoft.Json
How can I deserialize complex json repsonse into easy-to-deserialize structure using Newtonsoft.Json

Time:02-02

Having this strange-looking json response:

{
    "invoices":{
       "0":{
          "invoice":{
             "id":"420",
             "invoicecontents":{
                "0":{
                   "invoicecontent":{
                      "name":"Here's the name of the content 0"
                   }
                },
                "1":{
                   "invoicecontent":{
                      "name":"Here's the name of the content 1"
                   }
                }
             }
          }
       },
       "1":{
         "invoice":{
            "id":"420",
            "invoicecontents":{
               "0":{
                  "invoicecontent":{
                     "name":"Here's the name of the content 0"
                  }
               }
            }
         }
      },
       "parameters":{
          "limit":"3",
          "page":"1",
          "total":"420"
       }
    },
    "status":{
       "code":"OK"
    }
 }

How do I change the structure into this easy-to-deserialize one?

{
   "invoices":[
      {
         "id":"420",
         "invoicecontents":[
            {
               "name":"Here's the name of the content 0"
            },
            {
               "name":"Here's the name of the content 1"
            }
         ]
      },
      {
         "id":"420",
         "invoicecontents":[
            {
               "name":"Here's the name of the content 0"
            }
         ]
      }
   ]
}

I'd like to deserialize into List of Invoices as below

class Invoice {
    public string Id { get; set; }
    
    [JsonProperty("invoicecontents")]
    public InvoiceContent[] Contents { get; set; }

    class InvoiceContent {
        public string Name { get; set; }
    }
}

There's no problem with getting the status code or parameters, I simply do this:

var parsed = JObject.Parse(jsonInvoices);

var statusCode = parsed?["status"]?["code"]?.ToString();
var parameters = parsed?["invoices"]?["parameters"]?;

The real problem starts when I'm trying to achieve easy-to-deserialize json structure I've mentioned before. I've tried something like this:

var testInvoices = parsed?["invoices"]?
    .SkipLast(1)
    .Select(x => x.First?["invoice"]);

But I can't manage to "repair" invoicecontents/invoicecontent parts. My goal is to deserialize and store the data.

CodePudding user response:

This isn't JSON.

JavaScript Object Notation literally describes Objects. What you here is actually the punchline of a joke that starts out with: "A SQL JOIN, a StringBuilder, and a couple for loops walk into a bar..."

As others have demonstrated, JObject is great for working with JSON that would be impractical to define as classes. You can use Linq to navigate through it, but JSONPath Expressions are much, much simpler.

Example 1: Let's get the status code.

var status = parsed.SelectToken(".status.code").Value<string>();

Example 2: Let's 'deserialize' our invoices.


public string Invoice 
{ 
  public string Id { get; set; } 
  public List<string> Content { get; set; }
}

public List<Invoice> GetInvoices(string badJson)
{
  var invoices = JObject.Parse(badJson).SelectTokens(".invoices.*.invoice");
  var results = new List<Invoice>();
  foreach (var invoice in invoices)
  {
    results.Add(new Invoice()
    {
      Id = invoice.Value<string>("id"),
      Contents = invoice.SelectTokens(".invoicecontents.*.invoicecontent.name")
        .Values<string>().ToList()
      // Note: JToken.Value<T> & .Values<T>() return nullable types
    });
  }
  return results;
}

CodePudding user response:

try this

     var jsonParsed = JObject.Parse(json);
    
    var invoices = ((JObject) jsonParsed["invoices"]).Properties()
    .Select(x => (JObject) x.Value["invoice"] ).Where(x => x != null)
    .Select(s => new Invoice
    {
        Id = s.Properties().First(x => x.Name == "id").Value.ToString(),
        Contents = ((JObject)s.Properties().First(x => x.Name == "invoicecontents").Value).Properties()
        .Select(x => (string) x.Value["invoicecontent"]["name"]).ToList()
    }).ToList();

and I simplified your class too, I don't see any sense to keep array of classes with one string, I think it should be just array of strings

public class Invoice
{
    public string Id { get; set; }

    public List<string> Contents { get; set; }
}

result

[
  {
    "Id": "420",
    "Contents": [
      "Here's the name of the content 0",
      "Here's the name of the content 1"
    ]
  },
  {
    "Id": "420",
    "Contents": [
      "Here's the name of the content 0"
    ]
  }
]
  •  Tags:  
  • Related