I am new to JSON.NET and not sure how to do this.
I have the CartItem class, and I need to implement GetAllCartItemsFromArbitraryJson(string jsonStr), as follows:
class CartItem {
public int Id;
public int Qty;
}
List<CartItem> items = GetAllCartItemsFromArbitraryJson(jsonStr);
All I know is jsonStr contains one or more cartitems somewhere (I don't know how deep), eg.
{
... : {
"cartitems": [{
"id": "1",
"qty": "1"
},{
"id": "2",
"qty": "5"
}
]
},
... : {
... : {
...,
"cartitems": [{
"id": "10",
"qty": "2"
}
]
}
}
}
This function needs to collect all the cartitems and put it in List<CartItem>
List<CartItem> GetAllCartItemsFromArbitraryJson(string jsonStr) {
JObject json = JObject.Parse(jsonStr);
// then what...?
}
So the List<CartItem> will contains:
Id Qty
1 1
2 5
10 2
How would you do it in C#?
This is a solution that works:
List<CartItem> GetAllCartItemsFromArbitraryJson(string jsonStr) {
JObject json = JObject.Parse(jsonStr);
return json.Descendants().OfType<JProperty>() // so we can filter by p.Name below
.Where(p => p.Name == "cartitems")
.SelectMany(p => p.Value) // selecting the combined array (joined into a single array)
.Select(item => new CartItem {
Id = (int)item["id"],
Qty = (int)item["qty"]
}).ToList();
}
Hope it helps someone.
I am new to JSON though, got that by trial & error. So let me know if it can be improved :)
Parse to dynamic and iterate through the items.
public List<CartItem> PostAllCartItemsFromArbitraryJson(string jsonStr)
{
List<CartItem> AllCartItems = new List<CartItem>();
try
{
dynamic BaseJson = JObject.Parse(jsonStr.ToLower());
CheckForCarts(AllCartItems, BaseJson);
}
catch (Exception Error)
{
}
return AllCartItems;
}
private void CheckForCarts(List<CartItem> AllCartItems, dynamic BaseJson)
{
foreach (dynamic InnerJson in BaseJson)
{
if (InnerJson.Name == "cartitems")
{//Assuming this is an [] of cart items
foreach (dynamic NextCart in InnerJson.Value)
{
try
{
CartItem FoundCart = new CartItem();
FoundCart.Id = NextCart.id;
FoundCart.Qty = NextCart.qty;
AllCartItems.Add(FoundCart);
}
catch (Exception Error)
{
}
}
}
else if (InnerJson.Value is JObject)
{
CheckForCarts(AllCartItems, InnerJson.Value);
}
}
}
public class CartItem
{
public int Id;
public int Qty;
}
For your sample input this generates:
[{"Id":1,"Qty":1},{"Id":2,"Qty":5},{"Id":10,"Qty":2}]
Related
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.
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;
}
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"
]
}
]
Can someone help me with the following:
I have the following JSON that is composed of several arrays of AA, BB, CC objects and each of these has its corresponding KEY "ts" and VALUE "value" objects.
{
"AA": [
{
"ts": 1636862399574,
"value": "2021-11-14 00:57:25.049983"
},
{
"ts": 1636862398995,
"value": "2021-11-14 00:57:24.049979"
}
],
"BB": [
{
"ts": 1636862399574,
"value": "16183.8"
},
{
"ts": 1636862398995,
"value": "16183.8"
}
],
"CC": [
{
"ts": 1636862399574,
"value": "0.0"
},
{
"ts": 1636862398995,
"value": "0.0"
}
]
}
My code snippet in C #:
List<List<DateTime>> ArrayAA;
List<List<double>> ArrayBB;
List<List<double>> ArrayCC;
for (int i = 0; i < devices.Count; i++)
{
var data = JsonConvert.DeserializeObject(json);
if (data != null)
{
// Then deserialize your json to Dictionary<string, List<MyObject>>
var myDictionary = JsonConvert.DeserializeObject<Dictionary<string, List<MyObject>>>(JSONdata);
foreach (KeyValuePair<string, List<MyObject>> entry in myDictionary)
{
foreach (var obj in entry.Value)
{
Console.WriteLine($"{entry.Key} -> ts: {obj.TS}, value: {obj.Value}");
ArrayAA[i].Add(obj.value);
ArrayBB[i].Add(obj.value);
ArrayCC[i].Add(obj.value);
}
}
}
}
My code is not working, I need to read all the values at once for each AA, BB, CC.
It's possible?
You should first deserialize your object to a dictionary. This way you can iterate over each of the keys you have. Once you have the dictionary... you can print the values however you like.
this is an example that shows you how to access the values of each of the keys, "AA", "BB", "CC" etc.
// Build your object class first.
public class MyObject
{
[JsonProperty("ts")]
public long TS { get; set; }
[JsonProperty("value")]
public string Value { get; set; }
}
// Then deserialize your json to Dictionary<string, List<MyObject>>
var myDictionary = JsonConvert.DeserializeObject<Dictionary<string, List<MyObject>>>(jsonText);
foreach (KeyValuePair<string, List<MyObject>> entry in myDictionary)
{
foreach (var obj in entry.Value)
{
Console.WriteLine($"{entry.Key} -> ts: {obj.TS}, value: {obj.Value}");
}
}
List<DateTime> arrayAA = myDictionary["AA"].Select(x => DateTime.Parse(x.Value)).ToList();
List<double> arrayBB = myDictionary["BB"].Select(x => double.Parse(x.Value)).ToList();
List<double> arrayCC = myDictionary["CC"].Select(x => double.Parse(x.Value)).ToList();
// Prints the following output
AA -> ts: 1636862399574, value: 2021-11-14 00:57:25.049983
AA -> ts: 1636862398995, value: 2021-11-14 00:57:24.049979
BB -> ts: 1636862399574, value: 16183.8
BB -> ts: 1636862398995, value: 16183.8
CC -> ts: 1636862399574, value: 0.0
CC -> ts: 1636862398995, value: 0.0
UPDATE - How to add items?
if you want to add items to the data... you will need to know which Key you want to add the item to.
string key = "AA";
if (myDictionary.ContainsKey(key))
{
myDictionary[key].Add(new MyObject() { TS = 651651651651, Value = "abc" });
}
// or get only the values and save them in separate arrays? use LINQ
string[] arrayAA = myDictionary["AA"].Select(x => x.Value).ToArray();
string[] arrayBB = myDictionary["BB"].Select(x => x.Value).ToArray();
There are several ways to do this, one of which is to use the JObject class
JObject data = JObject.Parse(json);
if (data != null)
{
var itemsA = data["AA"].Select(i => i["value"]);
foreach(var item in itemsA)
{
Console.WriteLine("Value: " + item);
}
//and BB and CC.....................................
}
OR
JObject data = JObject.Parse(json);
Console.WriteLine("Value: " + data["AA"][0]["value"]);
Console.WriteLine("Value: " + data["AA"][1]["value"]);
//.......................................
Console.WriteLine("Value: " + data["BB"][0]["value"]);
You can either use dynamic data type or pack in specific classes.
Another way is to create class based on the json string and the deserialize to that class
using Newtonsoft.Json;
public class MyRootClass
{
public List<AA> AA { get; set; }
public List<AA> BB { get; set; }
public List<AA> CC { get; set; }
}
public class AA
{
public long ts { get; set; }
public string value { get; set; }
}
public class BB
{
public long ts { get; set; }
public string value { get; set; }
}
public class CC
{
public long ts { get; set; }
public string value { get; set; }
}
string json = "yourJson";
var myObject = JsonConvert.DeserializeObject<MyRootClass>(json);
and to read it. loop over each list in the myobject
foreach (var item in myObject.AA)
{
Console.WriteLine(item.value);
Console.WriteLine(item.ts);
}
My Json Response is Following below:
{"d":
{"RowData":
[{"GenreId":11,"GenreName":"Musical","subjecturl":"subjecturl_1","logourl":"logourl_1"},
{"GenreId":12,"GenreName":"kids","subjecturl":"subjecturl_2","logourl":"logourl_2"},
{"GenreId":13,"GenreName":"other","subjecturl":"subjecturl_3","logourl":"logourl_3"},
{"GenreId":14,"GenreName":"Musical","subjecturl":"subjecturl_4","logourl":"logourl_4"},
{"GenreId":15,"GenreName":"Music","subjecturl":"subjecturl_5","logourl":"logourl_5"},
{"GenreId":16,"GenreName":"Faimaly","subjecturl":"subjecturl_6","logourl":"logourl_6"},
{"GenreId":17,"GenreName":"other","subjecturl":"subjecturl_7","logourl":"logourl_7"},
{"GenreId":18,"GenreName":"other","subjecturl":"subjecturl_8","logourl":"logourl_8"},
{"GenreId":19,"GenreName":"kids","subjecturl":"subjecturl_9","logourl":"logourl_9"},
{"GenreId":20,"GenreName":"Musical","subjecturl":"subjecturl_10","logourl":"logourl_10"},
{"GenreId":21,"GenreName":"other","subjecturl":"subjecturl_11","logourl":"logourl_11"}]}}
Using the above Response I tried to make like below Response :
{"rows": [{
"title": "Musical",
"items": [{"hdsubjecturl": "subjecturl_1"},{"hdsubjecturl": "subjecturl_4"},{"hdsubjecturl": "subjecturl_10"}]
},{
"title": "kids",
"items": [{"hdsubjecturl": "subjecturl_2"},{"hdsubjecturl": "subjecturl_9"}]
},{
"title": "Music",
"items": [{"hdsubjecturl": "subjecturl_5"}]
},{
"title": "other",
"items": [{"hdsubjecturl": "subjecturl_3"},{"hdsubjecturl": "subjecturl_7"},{"hdsubjecturl": "subjecturl_8"},{"hdsubjecturl": "subjecturl_11"}]
},{
"title": "Faimaly",
"items": [{"hdsubjecturl": "subjecturl_6"}]
}]
}
My Code is below :
JObject Root = JObject.Parse(jsonData["d"].ToString());
var unique = Root["RowData"].GroupBy(x => x["GenreName"]).Select(x => x.First()).ToList(); // here fetch 5 record
foreach (var un in unique)
{
var GenreName = new
{
title = un["GenreName"],
items = new
{
hdsubjecturl = "logourl"
}
};
var GenreNamereq = JsonConvert.SerializeObject(GenreName, Newtonsoft.Json.Formatting.Indented);
genstr.Append(GenreNamereq, 0, GenreNamereq.Length);
genstr.Append(",");
using (System.IO.StreamWriter file = new System.IO.StreamWriter(subdir + "\\GenreName.json"))
{
string st = genstr.ToString().Substring(0, (genstr.Length - 1));
file.WriteLine("{\n\"rows\": [\n" + st + "\n}"); //seasion number 21 terminate
file.Close();
}
}
Using above code my output is below for First Field :
{"rows":
[{
"title": "Musical",
"items": {
"hdsubjecturl": "logourl"
}
}]
}
Using below code I tried to fetch Multiple values using specific Field :
List<string> CategoryList = new List<string>();
var unique = Root["RowData"].GroupBy(x => x["GenreName"]).Select(x => x.First()).ToList(); // here fetch 8 record
foreach (var cat in unique)
{
CategoryList.Add(cat["GenreName"].ToString());
}
List<List<string>> myList = new List<List<string>>();
for (int i=0;i<CategoryList.Count();i++)
{
var results = from x in Root["RowData"]
where x["GenreName"].Value<string>() == CategoryList[i]
select x;
foreach (var token in results)
{
Console.WriteLine(token["logourl"]);
}
// myList.Add(results);
}
In the First code, I used JObject for fetching a Root node. But, using the above query it takes by default JTocken. So, I used foreach loop here.
I used Dictionary instances for JSON Creation. Using this code I successfully fetched hdsubjecturl in for loop. But, I don't know how to put multiple values in Dictionary instances. Because I get title fields only single times using a unique query and items fields inside a hdsubjetcurl is more than one. Does anyone know how it's possible?
You can group your RowData by GenreName token, then use ToDictionary method to get a result dictionary and map it to desired structure (with title and hdsubjecturl). Finally create a result object using JObject.FromObject
var json = JObject.Parse(jsonString);
var data = json["d"]?["RowData"]
.GroupBy(x => x["GenreName"], x => x["subjecturl"])
.ToDictionary(g => g.Key, g => g.ToList())
.Select(kvp => new { title = kvp.Key, items = kvp.Value.Select(x => new { hdsubjecturl = x }) });
var result = JObject.FromObject(new { rows = data });
Console.WriteLine(result);
It gives you the expected result.
Edit: according to comments, GroupBy and Select expressions should be updated to map more then one property in result title item
var data = json["d"]?["RowData"]
.GroupBy(x => x["GenreName"])
.ToDictionary(g => g.Key, g => g.ToList())
.Select(kvp => new
{
title = kvp.Key,
items = kvp.Value.Select(x => new { hdsubjecturl = x["subjecturl"], url = x["logourl"] })
});
var result = JObject.FromObject(new { rows = data });
Consider trying this code, (using Newtonsoft Json deserializer);
public partial class Root
{
public D D { get; set; }
}
public partial class D
{
public RowDatum[] RowData { get; set; }
}
public partial class RowDatum
{
public long GenreId { get; set; }
public string GenreName { get; set; }
public string Subjecturl { get; set; }
public string Logourl { get; set; }
}
public partial class Response
{
public Row[] Rows { get; set; }
}
public partial class Row
{
public string Title { get; set; }
public Item[] Items { get; set; }
}
public partial class Item
{
public string Hdsubjecturl { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
var json =
#"{""d"":{""RowData"":[{""GenreId"":11,""GenreName"":""Musical"",""subjecturl"":""subjecturl_1"",""logourl"":""logourl_1""},{""GenreId"":12,""GenreName"":""kids"",""subjecturl"":""subjecturl_2"",""logourl"":""logourl_2""},{""GenreId"":13,""GenreName"":""other"",""subjecturl"":""subjecturl_3"",""logourl"":""logourl_3""},{""GenreId"":14,""GenreName"":""Musical"",""subjecturl"":""subjecturl_4"",""logourl"":""logourl_4""},{""GenreId"":15,""GenreName"":""Music"",""subjecturl"":""subjecturl_5"",""logourl"":""logourl_5""},{""GenreId"":16,""GenreName"":""Faimaly"",""subjecturl"":""subjecturl_6"",""logourl"":""logourl_6""},{""GenreId"":17,""GenreName"":""other"",""subjecturl"":""subjecturl_7"",""logourl"":""logourl_7""},{""GenreId"":18,""GenreName"":""other"",""subjecturl"":""subjecturl_8"",""logourl"":""logourl_8""},{""GenreId"":19,""GenreName"":""kids"",""subjecturl"":""subjecturl_9"",""logourl"":""logourl_9""},{""GenreId"":20,""GenreName"":""Musical"",""subjecturl"":""subjecturl_10"",""logourl"":""logourl_10""},{""GenreId"":21,""GenreName"":""other"",""subjecturl"":""subjecturl_11"",""logourl"":""logourl_11""}]}}";
var root = JsonConvert.DeserializeObject<Root>(json);
var rows = root.D.RowData.ToLookup(d => d.GenreName)
.Select(g => new Row()
{
Title = g.Key,
Items = g.ToList().Select(rd => new Item() {Hdsubjecturl = rd.Logourl}).ToArray()
}).ToArray();
var response = new Response()
{
Rows = rows
}; // reponse is the type of Json Response you wanted to achieve
Console.WriteLine();
}
}
I am using c#,along with MongoDB.
I have a class that can be resembled by this.
Its a sample, that represents something, please dont comment on the class design
[CollectionName("Venues")]
public class Venue
{
public string Name { get; set; }
public dictionary<string,object> Properties { get; set; }
}
var venue = new Venue
{
Name = "Venue 1",
Properties = new Dictionary<string,object>
{
{ "Chairs", "18" },
{ "Tables", "4" },
{ "HasWaterfall", true }
}
}
Assuming I have an object in a collection, that looks like that.
I would like to find out of it is possible to do two things.
1: Load from the database, only a single item from the dictionary,
currently I can only see how this can be done, by loading the entire
record from the database and then manually getting the value by key.
2: Determine the average of a single item within the database.
For example, across all records I would like to work out the average
chairs, again without loading all records and then doing it in memory with
linq etc....
Basically your sample document gets stored as a below JSON:
{
"_id" : ObjectId("..."),
"Name" : "Venue 1",
"Properties" : {
"Chairs" : "18",
"Tables" : "4",
"HasWaterfall" : true
}
}
This gives you a possibility to define a projection using dot notation:
var filter = Builders<Venue>.Filter.Eq(f => f.Name, "Venue 1");
var projection = Builders<Venue>.Projection.Include("Properties.Chairs");
List<BsonDocument> data = Col.Find(filter).Project(projection).ToList();
which returns below following BsonDocument:
{ "_id" : ObjectId("..."), "Properties" : { "Chairs" : "18" } }
To get the average you need to use $toInt operator introduced in MongoDB 4.0 to convert your values from string to int. Try:
var project = new BsonDocument()
{
{ "chairs", new BsonDocument() { { "$toInt", "$Properties.Chairs" } } }
};
var group = new BsonDocument()
{
{ "_id", "null" },
{ "avg", new BsonDocument() { { "$avg", "$chairs" } } }
};
var avg = Col.Aggregate().Project(project).Group(group).First();
here's an alternative way of doing it using MongoDB.Entities convenience library.
using System.Collections.Generic;
using System.Linq;
using MongoDB.Entities;
namespace StackOverflow
{
class Program
{
[Name("Venues")]
public class Venue : Entity
{
public string Name { get; set; }
public Dictionary<string, object> Properties { get; set; }
}
static void Main(string[] args)
{
new DB("test");
var venue1 = new Venue
{
Name = "Venue 1",
Properties = new Dictionary<string, object> {
{ "Chairs", 28 },
{ "Tables", 4 },
{ "HasWaterfall", true }
}
};
venue1.Save();
var venue2 = new Venue
{
Name = "Venue 2",
Properties = new Dictionary<string, object> {
{ "Chairs", 38 },
{ "Tables", 4 },
{ "HasWaterfall", true }
}
};
venue2.Save();
var chairs = DB.Find<Venue, object>()
.Match(v => v.Name == "Venue 1")
.Project(v => new { ChairCount = v.Properties["Chairs"] })
.Execute();
var avgChairs = DB.Collection<Venue>()
.Average(v => (int)v.Properties["Chairs"]);
}
}
}
results in the following queries being made to the database:
getting chairs in venue 1:
db.runCommand({
"find": "Venues",
"filter": {
"Name": "Venue 1"
},
"projection": {
"Properties.Chairs": NumberInt("1"),
"_id": NumberInt("0")
},
"$db": "test"
})
getting average chair count across all venues:
db.Venues.aggregate([
{
"$group": {
"_id": NumberInt("1"),
"__result": {
"$avg": "$Properties.Chairs"
}
}
}
])
I have JSON:
{
"One": [
{
"ID": 1,
"name": "s"
},
{
"categoryID": 2,
"name": "c"
}
],
"Two": [
{
"ID": 3,
"name": "l"
}
],
"Three": [
{
"ID": 8,
"name": "s&P"
},
{
"ID": 52,
"name": "BB"
}
]
}
I want to:
Take this JSON to any object(like JObject)
Filter the JSON on different conditions(like name start with s, etc
Return this JSON to client
Things I have tried:
1. Creating models:
class Model
{
public int Id;
public string name;
}
class MainModel
{
public string mainName;
public List<Model> categories;
}
And use these model to:
List<MainModel> m = json_serializer.DeserializeObject(jsonString);
I tried to Dictionary also but getting unable to cast exception.
any help would be appreciated.
The following will allow you to deserialize your JSON to a Dictionary and filter on it. This uses Newtonsoft.Json Nuget package, but that's fairly common. Code posted below. Working example found here .Net Fiddle.
using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var json = "{\"One\": [{ \"ID\": 1, \"name\": \"s\"},{ \"categoryID\": 2, \"name\": \"c\"}],\"Two\": [{ \"ID\": 3, \"name\": \"l\"}],\"Three\": [{ \"ID\": 8, \"name\": \"s&P\"},{ \"ID\": 52, \"name\": \"BB\"}]}";
var deserialized = JsonConvert.DeserializeObject<Dictionary<string, List<Model>>>(json);
Console.WriteLine(deserialized["One"][0].name);
Console.WriteLine("Filter to starts with s");
var filtered = deserialized.SelectMany(item => item.Value).Where(innerItem => innerItem.name.StartsWith("s"));
foreach(var item in filtered){
Console.WriteLine(item.name);
}
}
public class Model{
public int ID {get;set;}
public string name {get;set;}
public int categoryID {get;set;}
}
}
I have written this code assuming that:
One, Two and Three are a MainCategory -which is seralized as JProperty from a KeyValuePair<string name, List<SubCategory> values>. Long type name, eh? Add the following if you want to use the result dictionary often:
using MainCategories = System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<JsonDeSerializtionTest.SubCategory>>;
categoryID and ID are properties that descibe an Id and can not co-exist on the same SubCategory but do change the type of SubCategory.
1. Create the SubCategory class:
public class SubCategory
{
[JsonIgnore]
public DataTypes DataType { get; set; }
[JsonIgnore]
public string Parent { get; set; }
[JsonProperty("ID")]
public int Id { get; set; }
[JsonProperty("categoryID")]
public int CategoryId => Id;
[JsonProperty("name")]
public string Name { get; set; }
public bool ShouldSerializeId()
{
return DataType == DataTypes.Id;
}
public bool ShouldSerializeCategoryId()
{
return DataType == DataTypes.CategoryId;
}
}
2. Create the Deserialize(string json) function:
private static MainCategories Deserialize(string json)
{
Dictionary<string, List < SubCategory >> jsonBody = new Dictionary<string, List<SubCategory>>();
JObject jObject = JObject.Parse(json);
// the outer object {one...two..}
foreach (KeyValuePair<string, JToken> jMainCategory in jObject)
{
// jMainCategory => "one": [{...}, {...}]
string key = jMainCategory.Key;
List<SubCategory> values = new List<SubCategory>();
foreach (JObject jSubCategory in jMainCategory.Value)
{
//jsubCategory => {"name" : ..., "ID": ... }
SubCategory subCategory = new SubCategory();
JToken idProperty;
if (jSubCategory.TryGetValue("ID", out idProperty))
{
subCategory.DataType = DataTypes.Id;
subCategory.Id = idProperty.Value<int>();
}
else
{
subCategory.DataType = DataTypes.CategoryId;
subCategory.Id = jSubCategory["categoryID"].Value<int>();
}
subCategory.Name = jSubCategory["name"].Value<string>();
subCategory.Parent = key;
// subCategory.AnotherProperty = jSubCategory["anotherproperty"].Value<type>();
values.Add(subCategory);
}
jsonBody.Add(key, values);
}
return jsonBody;
}
3. Use the function to get a Dictionary<string, List<SubCategory>> which you can use for sorting and filtering. Example:
public static MainCategories WhereNameStartsWith(this MainCategories jsonBody, string str)
{
MainCategories result = new MainCategories();
//if you want to keep the result json structure `as is` return a MainCategories object
foreach (var subCategory in jsonBody.SelectMany(mainCategory => mainCategory.Value).Where(subCategory => subCategory.Name.StartsWith(str)))
{
if(result.ContainsKey(subCategory.Parent))
result[subCategory.Parent].Add(subCategory);
else
result.Add(subCategory.Parent, new List<SubCategory> {subCategory});
}
// if you just want the subcategories matching the condition create a WhereListNameStartsWith method
// where `result` is a list of subcategories matching the condition
return result;
}
Upside:
No playing around with NewtonSoft JsonConverter or ContractResolvers.
The ability to use LINQ to easily sort Name AND CategoryId/Id which have the same value but make a different SubCategory.DataType:
foreach (var subCategory in jsonBody.SelectMany(mainCategory => mainCategory.Value).Where(subCategory => subCategory.DataType == DataTypes.CategoryId))
You can easily serialize the JSON back to string representation. Example:
string jsonIn ="{\"One\":[{\"ID\":1,\"name\":\"s\"}," +
"{\"categoryID\":2,\"name\":\"c\"}]," +
"\"Two\":[{\"ID\":3,\"name\":\"l\"}]," +
"\"Three\":[{\"ID\":8,\"name\":\"s&P\"}," +
"{\"ID\":52,\"name\":\"BB\"}]}";
MainCategories desrializedJson = Deserialize(jsonIn);
MainCategories filtered = desrializedJson.WhereNameStartsWith("s");
string jsonOut = JsonConvert.SerializeObject(desrializedJson, Formatting.None);
Debug.Assert(jsonOut == jsonIn); //true
Removes the need for null checking when accessing Id/CategoryId.
My favorite: using C# syntax for property names.
Downside:
I don't know. This is a paste and forget soltuion :P
Probably a harder time when changing your JSON structure (although I might argue it would make things much easier for you)
Notes:
Parent is optional and is used to make things easier when using LINQ
DataTypes is an enum which is used to determine wether ID or categoryID was found if you want such information and also to serialize the proper property (identical two way convertion)
public enum DataTypes
{
Id,
CategoryId
};