Reputation: 324
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
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
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