Build JSON Hierarchy from Structured Data - c#

C# | .NET 4.5 | Entity Framework 5
I have data coming back from a SQL Query in the form of ID,ParentID,Name. I'd like to take that data and parse it into a Hierarchical JSON string. So far it seems to be much more of a daunting task than it should be. Since I'm using Entity the data comes back nicely to me as an IEnumerable. Now I believe I just need some form of recursion, but I'm not quite sure where to start. Any help is appreciated.
Data Returns as
id parentId name
1 1 TopLoc
2 1 Loc1
3 1 Loc2
4 2 Loc1A
Code is
public static string GetJsonLocationHierarchy(long locationID)
{
using (EntitiesSettings context = new EntitiesSettings())
{
// IEnumerable of ID,ParentID,Name
context.GetLocationHierarchy(locationID);
}
}
The end result I'd hope would be something like this:
{
"id": "1",
"parentId": "1",
"name": "TopLoc",
"children": [
{
"id": "2",
"parentId": "1",
"name": "Loc1",
"children": [
{
"id": "4",
"parentId": "2",
"name": "Loc1A",
"children": [
{}
]
}
]
},
{
"id": "3",
"parentId": "1",
"name": "Loc2",
"children": [
{}
]
}
]
}

One way to turn a flat table into a hierarchy is to put all your nodes into a dictionary. Then iterate over the dictionary, and for each node, look up its parent and add it to the parent's children. From there, you just need to find the root and serialize it.
Here is an example program to demonstrate the approach:
class Program
{
static void Main(string[] args)
{
IEnumerable<Location> locations = new List<Location>
{
new Location { Id = 1, ParentId = 1, Name = "TopLoc" },
new Location { Id = 2, ParentId = 1, Name = "Loc1" },
new Location { Id = 3, ParentId = 1, Name = "Loc2" },
new Location { Id = 4, ParentId = 2, Name = "Loc1A" },
};
Dictionary<int, Location> dict = locations.ToDictionary(loc => loc.Id);
foreach (Location loc in dict.Values)
{
if (loc.ParentId != loc.Id)
{
Location parent = dict[loc.ParentId];
parent.Children.Add(loc);
}
}
Location root = dict.Values.First(loc => loc.ParentId == loc.Id);
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Formatting.Indented
};
string json = JsonConvert.SerializeObject(root, settings);
Console.WriteLine(json);
}
}
class Location
{
public Location()
{
Children = new List<Location>();
}
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
public List<Location> Children { get; set; }
}
Here is the output:
{
"id": 1,
"parentId": 1,
"name": "TopLoc",
"children": [
{
"id": 2,
"parentId": 1,
"name": "Loc1",
"children": [
{
"id": 4,
"parentId": 2,
"name": "Loc1A",
"children": []
}
]
},
{
"id": 3,
"parentId": 1,
"name": "Loc2",
"children": []
}
]
}

Related

Newtonsoft Json - Serialize Deep nested object based on given selected properties

Does Newtonsoft Json have any way to serialize nested objects with only selected properties ?
The model looks like:
class Node
{
int id{ get; set; };
string name{ get; set; };
IList<Node>children{ get; set; };
}
I am creating a tree structure afterwards which looks something like this:
{
"id": 1,
"name": "A",
"children": [
{
"id": 2,
"name": "B",
"children": null
},
{
"id": 3,
"name": "C",
"children": [
{
"id": 10,
"name": "D",
"children": null
}
]
}
]
}
Wrote my own serializer. I passed Children property only .It shows as:
{
"children": [
{
"children": null
},
{
"children": [
{
"children": null
}
]
}
]
}
I want to show all properties and not just children under 1st/root's children.Similar to below.
{
"children": [
{
"id": 2,
"name": "B",
"children": null
},
{
"id": 3,
"name": "C",
"children": [
{
"id": 10,
"name": "D",
"children": null
}
]
}
] }
Also how do I do the same for children.Id where I want to serialize all children.Id of root node,Similar to below
{
"children": [
{
"id": 2
},
{
"id": 3
}
]
}
they have done the same result you want to achieve on this page.
https://weblog.west-wind.com/posts/2012/aug/30/using-jsonnet-for-dynamic-json-parsing
Also, see Here,
https://entityframeworkcore.com/knowledge-base/58283745/self-reference-when-serializing-nested-object
I hope that will be useful.
What do you need your own serializer for? Try the standard one
var jsonDeserialized = JsonConvert.DeserializeObject<Child>(json);
json = JsonConvert.SerializeObject(jsonDeserialized);
Output
{
"id": 1,
"name": "A",
"children": [
{
"id": 2,
"name": "B",
"children": null
},
{
"id": 3,
"name": "C",
"children": [
{
"id": 10,
"name": "D",
"children": null
}
]
}
]
}
Classes
public partial class Child
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("children")]
public List<Child> Children { get; set; }
}
For this
{
"children": [
{
"id": 2,
"name": "B",
"children": null
},
{
"id": 3,
"name": "C",
"children": [
{
"id": 10,
"name": "D",
"children": null
}
]
}
] }
You can use
var jsonDeserialized = JsonConvert.DeserializeObject<Data>(json);
public partial class Data
{
[JsonProperty("children")]
public List<Child> Children { get; set; }
}

C# MongoDB Count with Lookup or Match

So Suppose I have two Collections
User {
string Id;
string name;
}
Category {
string Id;
string userId;
string name;
}
// For Look Up
UserJoined : User {
public ICOllection<Category> Categories {get;set;}
}
I am trying to count using this kind of Query (SQL)
Expression<Func<User,bool>> query = p => p.User.Category.Name == "Apple";
Now the above works great in SQL using EF Core.
But I want to do something similar in Mongo DB Driver C#.
The Current situation fetches all the documents and then it Takes count, which is very very bad performance wise in the long run. I know there might be something.
I was looking at AddFields operator but I couldn't find a way to use it in my case.
This should get the count of users which contain a category with the name "Apple":
MongoClient mongoClient = new MongoClient("mongodb://localhost:27017");
IMongoDatabase db = mongoClient.GetDatabase("your_db_name");
IMongoCollection<BsonDocument> usersCollection = db.GetCollection<BsonDocument>("users");
FilterDefinition<BsonDocument> matchFilter = Builders<BsonDocument>.Filter.ElemMatch<BsonDocument>("categories", Builders<BsonDocument>.Filter.Eq(c => c["name"], "Apple"));
AggregateCountResult result = usersCollection.Aggregate()
.Match(matchFilter)
.Count()
.Single();
long count = result.Count;
Which is a C# representation of the following aggregation:
db.users.aggregate(
[{
$match: {
categories: {
$elemMatch: {
name: "Apple"
}
}
}
}, {
$count: "count"
}]
)
Considering you have users collection with the following data:
[
{
"_id": 1,
"name": "User 1",
"categories": [
{
"_id": 1,
"name": "Orange"
},
{
"_id": 2,
"name": "Apple"
},
{
"_id": 3,
"name": "Peach"
}
]
},
{
"_id": 2,
"name": "User 2",
"categories": [
{
"_id": 1,
"name": "Orange"
},
{
"_id": 3,
"name": "Peach"
}
]
},
{
"_id": 3,
"name": "User 3",
"categories": [
{
"_id": 4,
"name": "Banana"
}
]
},
{
"_id": 4,
"name": "User 4",
"categories": [
{
"_id": 4,
"name": "Banana"
},
{
"_id": 2,
"name": "Apple"
}
]
},
{
"_id": 5,
"name": "User 5",
"categories": [
{
"_id": 2,
"name": "Apple"
}
]
}
]
Value of count variable will be 3.
if the goal is to get a count of users who has "Apple" as a category, you can simply do it like so:
var appleUserCount = await categoryCollection.CountDocumentsAsync(c=>c.Name == "Apple");
however, if there are duplicate categories (same userId and categoryName) then getting an accurate count gets a bit complicated and you'll have to do some grouping for getting a distinct user count.
test program:
using MongoDB.Entities;
using System.Threading.Tasks;
namespace TestApplication
{
public class User : Entity
{
public string Name { get; set; }
}
public class Category : Entity
{
public string UserID { get; set; }
public string Name { get; set; }
}
public static class Program
{
private static async Task Main()
{
await DB.InitAsync("test");
await DB.Index<Category>()
.Key(c => c.Name, KeyType.Ascending)
.CreateAsync();
var user1 = new User { Name = "Fanboy" };
var user2 = new User { Name = "Poolboy" };
await new[] { user1, user2 }.SaveAsync();
await new[] {
new Category { Name = "Apple", UserID = user1.ID},
new Category { Name = "Apple", UserID = user2.ID},
new Category { Name = "Google", UserID = user1.ID}
}.SaveAsync();
var count = await DB.CountAsync<Category>(c => c.Name == "Apple");
}
}
}

jsonResult with collectionname

Hi I would like to use the json collection from the code below in an angular repeater.
I need to give the collection a name but i dont know how
this is the code to generate the JSON
public JsonResult GetProducts()
{
var result = db.Products.ToList();
var list = JsonConvert.SerializeObject(result, Formatting.None, new JsonSerializerSettings(){ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore});
return Json(list,JsonRequestBehavior.AllowGet);
}
and this is the result
[{
"Category": {
"ID": 4,
"Name": "TEST"
},
"OrderDetails": [],
"ID": 10006,
"Description": "TEST",
"Name": "TEST",
"Price": 3.20,
"PictureUrl":"",
"CategoryId": 4,
"AddedToShop": "2016-12-11T14:52:57.677"
},
{
"Category": {
"ID": 4,
"Name": "TEST"
},
"OrderDetails": [],
"ID": 20005,
"Description": "TEST2",
"Name": "TEST2",
"Price": 3.20,
"PictureUrl":"",
"CategoryId": 4,
"AddedToShop": "2016-12-12T12:02:10.593"
}]
and I would like it to be like the code below so I can use the products tag to iterate .
{
"products": [{
"Category": {
"ID": 4,
"Name": "TEST"
},
"OrderDetails": [],
"ID": 10006,
"Description": "TEST",
"Name": "TEST",
"Price": 3.20,
"PictureUrl":"",
"CategoryId": 4,
"AddedToShop": "2016-12-11T14:52:57.677"
},
{
"Category": {
"ID": 4,
"Name": "TEST"
},
"OrderDetails": [],
"ID": 20005,
"Description": "TEST2",
"Name": "TEST2",
"Price": 3.20,
"PictureUrl":"",
"CategoryId": 4,
"AddedToShop": "2016-12-12T12:02:10.593"
}]}
If you are using WebAPI controllers you don't need to send JsonResult, all you need to do is create a class and send it back to client:
public class MyClass
{
public List<Product> Products { get; set; }
}
And in your controller:
public MyClass Get() {
return new MyClass {
Products = db.Products.ToList()
};
}
OR:
Simply create your object and then pass it in the serializer:
JsonConvert.SerializeObject( { "products": result }, Formatting.None, new JsonSerializerSettings(){ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore})
You can use
var list = JsonConvert.SerializeObject(new { products = result }, Formatting.None, new JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore });
One solution for your problem is, define a seperate class and define a property for that class as either a collection or list of the type "db.Products"(Use the appropriate class name for this object as you have in your code) and when you serialize that object you will get it as you have asked for
class ProductList
{
public Collection<YourType> Products{get; set;}
}
OR
class ProductList
{
public List<YourType> Products{get; set;}
}

Generate c# classes from json dynamically

I usually use json2csharp to generate json classes to c#. But I do have problem. My json is have dynamic depth like this
{
"kategori": [
{
"id": "1",
"namakategori": "Tips & Trick",
"parent_id": "0",
"children": [
{
"id": "348",
"namakategori": "Fotografi",
"parent_id": "1",
"children": []
},
{
"id": "370",
"namakategori": "Hacking",
"parent_id": "1",
"children": []
}
]
},
{
"id": "12",
"namakategori": "Aplikasi",
"parent_id": "0",
"children": [
{
"id": "13",
"namakategori": "Tools",
"parent_id": "12",
"children": [
{
"id": "14",
"namakategori": "Toolsorder",
"parent_id": "13",
"children":[]
},
]
},
]
},
]
}
So how do I generate json classes dynamically so it can be used for my json? In above example I have 3 depth. But if I go to different page maybe it have 4 or more depth.
You don't need to declere your classes dynamically. This should work:
public class Child
{
public string id { get; set; }
public string namakategori { get; set; }
public string parent_id { get; set; }
public List<Child> children { get; set; } // <-- See this
}
public class RootObj
{
public List<Child> kategori { set; get; }
}
To deserialize I'll use Json.Net
var res = JsonConvert.DeserializeObject<RootObj>(json);
You can always use the Newtonsoft.Json
For Instance,
JObject result = (JObject) JsonConvert.DeserializeObject(yourJsonDataHere);
var katObject = result.Property("kategori").Value;
and so on...
PS: Not sure if Newtonsoft.Json is supported on WP7.

umbraco content tree nodes to json format

I have an umbraco website with the following content structure:
(the text in brackets is the nodeTypeAlias)
[root]
- [child]
| - [module]
| | - [submodule]
| | - [submodule]
- [child]
| - [module]
| - [module]
| | - [submodule]
I am trying to export the above structure (together with the node's properties) into the following json file:
{
"root": {
"child": [
{
"id": "1",
"name": "Child 1",
"module": [
{
"id": "2",
"name": "Module 1",
"submodule": [
{
"id": "3",
"name": "Sub module 1"
},
{
"id": "4",
"name": "Sub module 3"
}
]
}
]
},
{
"id": "5",
"name": "Child 5",
"module": [
{
"id": "8",
"name": "Module 8"
},
{
"id": "6",
"name": "Module 6",
"submodule": [
{
"id": "7",
"name": "Sub module 7"
},
{
"id": "9",
"name": "Sub module 9"
}
]
}
]
}
]
}
}
So far I've wrote down the following code in Linqpad but the result is not the one that I was looking for.
List<Node> brands = new List<Node>()
{
new Node
{
id = 1,
name = "Brand 1",
type = "brand",
children = new List<Node>
{
new Node
{
id = 2,
name = "Shelf 1",
type = "shelf",
children = new List<Node>
{
new Node
{
id = 1,
name = "Bookcase 1",
type = "bookcase"
},
new Node
{
id = 2,
name = "Bookcase 2",
type = "bookcase"
},
new Node
{
id = 3,
name = "Bookcase 3",
type = "bookcase"
}
}
},
new Node
{
id = 3,
name = "Shelf 2",
type = "shelf",
children = new List<Node>
{
new Node
{
id=1,
type= "module",
name = "Module 1"
},
new Node
{
id=2,
type= "pdf",
name = "PDF 1"
},
new Node
{
id=3,
type= "link",
name = "Link 1"
},
new Node
{
id=4,
type= "link",
name = "Link 2"
},
}
}
}
},
new Node
{
id = 2,
name = "Brand 2",
type = "brand",
children = new List<Node>
{
new Node
{
id = 2,
name = "Shelf 1",
type = "shelf",
},
new Node
{
id = 3,
name = "Shelf 2",
type = "shelf",
}
}
}
};
Result container = new Result();
Action<List<Node>, Result> add = null;
add = (nodes, coll) =>
{
List<Result> list = null;
if(nodes != null && nodes.Count > 0)
{
nodes.Dump("nodes");
foreach(var node in nodes)
{
string key = node.type;
list = null;
if(coll.Children.ContainsKey(key))
{
list = coll.Children[key];
}
else
{
list = new List<Result>();
}
Result r = new Result();
r.Name = node.name;
add(node.children, r);
list.Add(r);
coll.Children[key] = list;
coll.Dump("coll");
}
}
};
add(brands, container);
container.Dump();
var serializer = new JavaScriptSerializer();
serializer.Serialize(container).Dump();
}
public class Result
{
public Result()
{
this.Children = new Dictionary<string, List<Result>>();
this.Properties = new Dictionary<string, string>();
}
public string Name {get;set;}
public Dictionary<string, string> Properties {get;set;}
public Dictionary<string, List<Result>> Children {get;set;}
}
public class Node
{
public string name{get;set;}
public string type {get;set;}
public int id {get;set;}
public List<Node> children{get;set;}
result:
{
"Name": null,
"Properties": {},
"Children": {
"brand": [
{
"Name": "Brand 1",
"Properties": {},
"Children": {
"shelf": [
{
"Name": "Shelf 1",
"Properties": {},
"Children": {
"bookcase": [
{
"Name": "Bookcase 1",
"Properties": {},
"Children": {}
},
{
"Name": "Bookcase 2",
"Properties": {},
"Children": {}
},
{
"Name": "Bookcase 3",
"Properties": {},
"Children": {}
}
]
}
},
{
"Name": "Shelf 2",
"Properties": {},
"Children": {
"module": [
{
"Name": "Module 1",
"Properties": {},
"Children": {}
}
],
"pdf": [
{
"Name": "PDF 1",
"Properties": {},
"Children": {}
}
],
"link": [
{
"Name": "Link 1",
"Properties": {},
"Children": {}
},
{
"Name": "Link 2",
"Properties": {},
"Children": {}
}
]
}
}
]
}
},
{
"Name": "Brand 2",
"Properties": {},
"Children": {
"shelf": [
{
"Name": "Shelf 1",
"Properties": {},
"Children": {}
},
{
"Name": "Shelf 2",
"Properties": {},
"Children": {}
}
]
}
}
]
}
}
Any idea?
Thanks.
This is very much the way that you would go about generating XML, but JSON is a little different. If you have updated your Umbraco to one of the later editions, then you use Razor to render JSON in this sort of way:
#{
var builder = new System.Text.StringBuilder();
foreach (var car in cars)
{
builder.Append("{");
builder.Append(Json.Encode("reg") + ":" + Json.Encode(car.Registration) + ",");
builder.Append(Json.Encode("model") + ":" + car.Model);
// INSERT OTHER VALUES HERE
builder.Append("}");
if (car.Registration != cars.Last().Registration)
{
builder.Append(",");
}
count++;
}
}
#Html.Raw(builder.ToString())
Another more interesting way is JSON.NET - a good option if you are using custom objects in your application. You populate your objects, and then pass them through JSON.NET. See here for more details.
Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Price = 3.99M;
product.Sizes = new string[] { "Small", "Medium", "Large" };
string json = JsonConvert.SerializeObject(product);
//{
// "Name": "Apple",
// "Expiry": "2008-12-28T00:00:00",
// "Price": 3.99,
// "Sizes": [
// "Small",
// "Medium",
// "Large"
// ]
//}
Product deserializedProduct = JsonConvert.DeserializeObject(json);
The beauty of this library is that you can also create strings and convert them to JSON. The only caveat is that you have to watch your JavaScript when trying to read the JSON. Things like single (') and double (") quotes can break your code.
string json = #"{
""Name"": ""Apple"",
""Expiry"": "2008-12-28T00:00:00",
""Price"": 3.99,
""Sizes"": [
""Small"",
""Medium"",
""Large""
]
}";
JObject o = JObject.Parse(json);
string name = (string)o["Name"];
// Apple
JArray sizes = (JArray)o["Sizes"];
string smallest = (string)sizes[0];
// Small
Your code could work if you used the System.Xml namespace (XmlDocument, XmlAttributes, XmlNode). It is unclear as to which Node object you are using but I will assume it is Umbraco's Node (I would work with a new DynamicNode() instead btw) and in which case you cannot just create them like that with populated properties. You have to create one in the Umbraco database before reading its values.
Hopefully this should give you something to go on with. Happy coding.

Categories