Michał Droździk
Michał Droździk

Reputation: 324

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

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.

Upvotes: 0

Views: 293

Answers (2)

Acktually
Acktually

Reputation: 76

This isn't JSON.

JavaScript Object Notation literally describes Objects. What you have here is 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 can be 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;
}

Upvotes: 2

Serge
Serge

Reputation: 43959

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"
    ]
  }
]

Upvotes: 1

Related Questions