Using JSON.Net / C# to read root value in json data - c#

Sorry for the nube-like question, but having been retired for sometime I find myself forgetting some things.
Given this sample json string:
{
"range": [
{ "num": 0 },
{ "num": 1 },
{ "num": 2 },
{ "num": 3 },
{ "num": 4 },
{ "num": 5 },
{ "num": 6 },
{ "num": 7 },
{ "num": 8 },
{ "num": 9 }
],
"friends": [
{
"id": 0,
"name": "Christian Cruz"
},
{
"id": 1,
"name": "Hunter Moon"
},
{
"id": 2,
"name": "Holden Gentry"
}
]
}
I would like to be able to read the root value ("range" and "friends" in this case) for each line in the data, then parse the remaining values.
void Main()
{
var json = File.ReadAllText(#"c:\data\sample.json");
JObject obj = JObject.Parse(json);
foreach(JProperty child in obj.Children())
{
}
}
Where I bogged down is as I iterate through the children collection (foreach(JProperty child ...) I can read the items in the array (e.g. "num", "id" and "name") but I am unable to read the root values (e.g. "range" and "friends")
Any help you could lend an old man would be very much appreciated.

It's much easier to deserialise it into C# objects, for example:
public class RootObject
{
public List<Range> range { get; set; }
public List<Friend> friends { get; set; }
}
public class Range
{
public int num { get; set; }
}
public class Friend
{
public int id { get; set; }
public string name { get; set; }
}
Now you can use deserialise like this:
var root = JsonConvert.DeserializeObject<RootObject>(json);
And use the data:
foreach (var range in root.Range)
{
//Do stuff
}
foreach (var friend in root.Friends)
{
//Do stuff
}

You can use SelectTokens , put the json in list and then iterate through JProperty.
var files = JObject.Parse(YourJson);
var recList = files.SelectTokens("$").ToList();
foreach (JProperty item in recList.Children())
{
var key = item.Name.ToString(); //store the root item here
var value = item.Value.ToString();
//Do your stuffs
}

Using this small recursive function below you can unpack JSON to see properties as well as values without making any classes
static void Main(string[] args)
{
var json = "{\"range\":[{\"num\":0},{\"num\":1},{\"num\":2},{\"num\":3},{\"num\":4},{\"num\":5},{\"num\":6},{\"num\":7},{\"num\":8},{\"num\":9}],\"friends\":[{\"id\":0,\"name\":\"Christian Cruz\"},{\"id\":1,\"name\":\"Hunter Moon\"},{\"id\":2,\"name\":\"Holden Gentry\"}]}";
JObject obj = JObject.Parse(json);
void UnpackJson(JToken jobj, int indent)
{
if (jobj == null)
return;
var name = (jobj as JProperty)?.Name;
if (name != null)
{
Console.Write(new string(' ', indent) + name + " :\n");
indent += 4;
}
foreach (var child in jobj.Children())
{
var chname = (child as JProperty)?.Name;
if (chname != null)
Console.Write(new string(' ', indent) + chname + " : ");
var value = (child as JProperty)?.Value;
if (child.Values().Count() > 1)
{
if (chname != null || name != null)
Console.WriteLine();
IEnumerable<JToken> jt = (value is JArray) ? child.Values() : child.Children();
foreach (var val in jt)
UnpackJson(val, indent + 4);
}
else
{
if (value != null)
Console.WriteLine(value);
}
}
}
UnpackJson(obj, 0);
Console.Read();
}
Output:
range :
num : 0
num : 1
num : 2
num : 3
num : 4
num : 5
num : 6
num : 7
num : 8
num : 9
friends :
id : 0
name : Christian Cruz
id : 1
name : Hunter Moon
id : 2
name : Holden Gentry

Related

I want to read json file data with specific json object array one by one using foreach loop in c#

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);
}

How to convert object which is like to json object to c#?

I have an object returned from API. It is very like to json but it is not. I printed it in visual studio immediate window. Something like.
[0]: {{
"projectId": 111,
"userId": 11,
"name": "Bill, Gates",
"value": "11"
}}
[1]: {{
"projectId": 222,
"userId": 22,
"name": "Cill, Gates",
"value": "22"
}}
// .....many
It is not json type. So I use cast
IEnumerable<MyClass> list = obj.Cast<object>();
public class MyClass
{
public int projectId {get; set;}
public int userId {get; set;}
public string name {get; set;}
public string value {get; set;}
}
However when I loop through the list
foreach(var item in list)
{
var temp = item.value;
}
I can't get item or temp. Hover it or print it in the immediate window; it shows
NullReferenceException
I assume that I have to convert the object to real json type rather then using cast<object>.
So what is the best way?
You can try something like this:
json = Regex.Replace(json, #"\[[0-9]+\]:", ""); // remove [0]:
json = json.Replace("{{", "{");
json = json.Replace("}}", "},");
json = $"[{json}]";
var obj = JsonConvert.DeserializeObject(json);
might need more work if you have more nested objects
Okay, so I was experimenting with your string and trying to use Regular Expressions and I have found a way to parse your string into your corresponding MyClass structure. I hope this helps you out:
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
public class Program
{
public static void Main()
{
string str = #"[0]: {{
'projectId': 111,
'userId': 11,
'name': 'Bill, Gates',
'value': '11'
}}
[1]: {{
'projectId': 222,
'userId': 22,
'name': 'Cill, Gates',
'value': '22'
}}";
try
{
showMatch(str, #"('(.*)':(.*)[',])");
}
catch(Exception ex)
{
Console.WriteLine("Exception occured: "+ex.Message);
}
}
static private void showMatch(string text, string expr)
{
//Declare what is required! Phew
List<MyClass> output = new List<MyClass>();
int projectId=0;
int userId=0;
string name=string.Empty;
string value=string.Empty;
var regEx = new Regex(#"[^0-9a-zA-Z]+");
//Start the matching process
MatchCollection mc = Regex.Matches(text, expr);
bool check;
foreach (Match m in mc)
{
check = false;
string[] parsed = m.ToString().Split(':');
var stripped = regEx.Replace(parsed[1], "");
if (parsed[0] == "'projectId'")
{
projectId = Convert.ToInt32(stripped);
}
if (parsed[0] == "'userId'")
{
userId = Convert.ToInt32(stripped);
}
if (parsed[0] == "'name'")
{
name = stripped;
}
if (parsed[0] == "'value'")
{
value = stripped;
check = true;
}
//Add to list
if(check==true)
{
output.Add(new MyClass { projectId = projectId, userId = userId, name = name, value = value });
}
}
output.Dump();
}
}
public class MyClass
{
public int projectId { get; set; }
public int userId { get; set; }
public string name { get; set; }
public string value { get; set; }
}
Output:
[
{
name : BillGates
projectId : 111
userId : 11
value : 11
},
{
name : CillGates
projectId : 222
userId : 22
value : 22
}
]
Working DEMO: https://dotnetfiddle.net/TCXVde

Using MongoDB C# driver find and update a node from parent children hierarchy

I have a hierarchical category document, like parent - Children - Children and so on....
{
id: 1,
value: {
}Children: [{
id: 2,
value: {
}Children: [{
id: 3,
value: {
}Children: [{
id: 4,
value: {
}Children: [{
id: 5,
value: {
}Children: [{
id: 6,
value: {
}Children: [{
id: 7,
value: {
}Children: []
}]
}]
}]
}]
}]
}]
}
In such documents, using MongoDB C# driver, how can I find a node where Id = x
I tried something like this
var filter = Builders<Type>.Filter.Eq(x => x.Id, 3);
var node = mongoDbRepository.GetOne(filter) ??
mongoDbRepository.GetOne(Builders<Type>.Filter.Where(x => x.Children.Any(c=>c.Id == 3)));
But this covers only two levels. In my example, I have 7 levels and I don't have a restriction on depth of level
Once I find that node I need to update that node.
MongoDB Documentation talks about hierarchical documents, but doesn't cover my scenario.
In your situation if you
don't have a restriction on depth of level
you can`t create update query. You must change schema for store data:
https://docs.mongodb.com/v3.2/tutorial/model-tree-structures/
If you depth is fixed:
public class TestEntity
{
public int Id { get; set; }
public TestEntity[] Value { get; set; }
}
class Program
{
static void Main()
{
const string connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
var db = client.GetDatabase("TestEntities");
var collection = db.GetCollection<TestEntity>("Entities");
collection.InsertOne(CreateTestEntity(1, CreateTestEntity(2, CreateTestEntity(3, CreateTestEntity(4)))));
const int selctedId = 3;
var update = Builders<TestEntity>.Update.AddToSet(x => x.Value, CreateTestEntity(9));
var depth1 = Builders<TestEntity>.Filter.Eq(x => x.Id, selctedId);
var depth2 = Builders<TestEntity>.Filter.Where(x => x.Value.Any(item => item.Id == selctedId));
var depth3 = Builders<TestEntity>.Filter.Where(x => x.Value.Any(item => item.Value.Any(item2 => item2.Id == selctedId)));
var filter = depth1 | depth2 | depth3;
collection.UpdateMany(filter, update);
// if you need update document on same depth that you match it in query (for example 3 as selctedId),
// you must make 2 query (bad approach, better way is change schema):
//var testEntity = collection.FindSync(filter).First();
//testEntity.Value[0].Value[0].Value = new[] {CreateTestEntity(9)}; //todo you must calculate depth what you need in C#
//collection.ReplaceOne(filter, testEntity);
}
private static TestEntity CreateTestEntity(int id, params TestEntity[] values)
{
return new TestEntity { Id = id, Value = values };
}
}
There seems to be something wrong in your example document. If the parent has 3 fileds: _id ,id and value, the example should be
{
"_id" : ObjectId("581bce9064989cce81f2b0c1"),
"id" : 1,
"value" : {
"Children" : [
{
"id" : 2,
"value" : {
"Children" : [
{
"id" : 3,
"value" : {
"Children" : [
{
"id" : 4,
"value" : {
"Children" : [
{
"id" : 5,
"value" : {
"Children" : [
{
"id" : 6,
"value" : {
"Children" : [
{
"id" : 7,
"value" : {
"Children" : []
}
}
]
}
}
]
}
}
]
}
}
]
}
}
]
}
}
]
}
}
Please try the following function, where target is the document and x is the id number you want to update.
bool FindAndUpdate2(BsonDocument target, int x)
{
BsonValue id, children;
while (true)
{
try
{
if (target.TryGetValue("_id", out children))
{
id = target.GetValue(1);
children = target.GetValue(3);
}
else
{
id = target.GetValue(0);
children = target.GetValue(2);
}
if (id.ToInt32() == x)
{
Update(target); //change as you like
return true; //success
}
else
target = children[0] as BsonDocument;
}
catch (Exception ex)
{
return false; //failed
}
}
}
else if the parent has 4 fileds: _id ,id, value and children the example should be
{
"_id" : ObjectId("581bdd3764989cce81f2b0c2"),
"id" : 1,
"value" : {},
"Children" : [
{
"id" : 2,
"value" : {},
"Children" : [
{
"id" : 3,
"value" : {},
"Children" : [
{
"id" : 4,
"value" : {},
"Children" : [
{
"id" : 5,
"value" : {},
"Children" : [
{
"id" : 6,
"value" : {},
"Children" : [
{
"id" : 7,
"value" : {},
"Children" : []
}
]
}
]
}
]
}
]
}
]
}
]
}
Then you can try this:
bool FindAndUpdate2(BsonDocument target, int x)
{
BsonValue id, children;
while (true)
{
try
{
if (target.TryGetValue("_id", out children))
{
id = target.GetValue(1);
children = target.GetValue(3);
}
else
{
id = target.GetValue(0);
children = target.GetValue(2);
}
if (id.ToInt32() == x)
{
Update(target); //change as you like
return true; //success
}
else
target = children[0] as BsonDocument;
}
catch (Exception ex)
{
return false; //failed
}
}
}
I have a version, which is based on #DmitryZyr 's answer, and uses 2 answer of the question How do I create an expression tree calling IEnumerable<TSource>.Any(...)?. Thanks to Aaron Heusser and Barry Kelly:
class Program
{
#region Copied from Expression.Call question
static MethodBase GetGenericMethod(Type type, string name, Type[] typeArgs, Type[] argTypes, BindingFlags flags)
{
int typeArity = typeArgs.Length;
var methods = type.GetMethods()
.Where(m => m.Name == name)
.Where(m => m.GetGenericArguments().Length == typeArity)
.Select(m => m.MakeGenericMethod(typeArgs));
return Type.DefaultBinder.SelectMethod(flags, methods.ToArray(), argTypes, null);
}
static bool IsIEnumerable(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}
static Type GetIEnumerableImpl(Type type)
{
// Get IEnumerable implementation. Either type is IEnumerable<T> for some T,
// or it implements IEnumerable<T> for some T. We need to find the interface.
if (IsIEnumerable(type))
return type;
Type[] t = type.FindInterfaces((m, o) => IsIEnumerable(m), null);
Debug.Assert(t.Length == 1);
return t[0];
}
static Expression CallAny(Expression collection, Expression predicateExpression)
{
Type cType = GetIEnumerableImpl(collection.Type);
collection = Expression.Convert(collection, cType); // (see "NOTE" below)
Type elemType = cType.GetGenericArguments()[0];
Type predType = typeof(Func<,>).MakeGenericType(elemType, typeof(bool));
// Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>)
MethodInfo anyMethod = (MethodInfo)
GetGenericMethod(typeof(Enumerable), "Any", new[] { elemType },
new[] { cType, predType }, BindingFlags.Static);
return Expression.Call(anyMethod, collection, predicateExpression);
}
#endregion
public class TestEntity
{
public int Id { get; set; }
public TestEntity[] Value { get; set; }
}
private static TestEntity CreateTestEntity(int id, params TestEntity[] values)
{
return new TestEntity { Id = id, Value = values };
}
static void Main(string[] args)
{
const string connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
var db = client.GetDatabase("TestEntities");
IMongoCollection<TestEntity> collection = db.GetCollection<TestEntity>("Entities");
collection.InsertOne(CreateTestEntity(1, CreateTestEntity(2, CreateTestEntity(3, CreateTestEntity(4)))));
const int selectedId = 4;
int searchDepth = 6;
// builds the expression tree of expanding x => x.Value.Any(...)
var filter = GetFilterForDepth(selectedId, searchDepth);
var testEntity = collection.Find(filter).FirstOrDefault();
if (testEntity != null)
{
UpdateItem(testEntity, selectedId);
collection.ReplaceOne(filter, testEntity);
}
}
private static bool UpdateItem(TestEntity testEntity, int selectedId)
{
if (testEntity.Id == selectedId)
{
return true;
}
if (UpdateItem(testEntity.Value[0], selectedId))
testEntity.Value[0] = CreateTestEntity(11);
return false;
}
private static FilterDefinition<TestEntity> GetFilterForDepth(int id, int depth)
{
// item
var idEqualsParam = Expression.Parameter(typeof(TestEntity), "item");
// .Id
var idProp = Expression.Property(idEqualsParam, "Id");
// item.Id == id
var idEquals = Expression.Equal(idProp, Expression.Constant(id));
// item => item.Id == id
var idEqualsLambda = Expression.Lambda<Func<TestEntity, bool>>(idEquals, idEqualsParam);
// x
var anyParam = Expression.Parameter(typeof(TestEntity), "x");
// .Value
var valueProp = Expression.Property(anyParam, "Value");
// Expression.Call would not find easily the appropriate .Any((TestEntity x) => x == id)
// .Value.Any(item => item.Id == id)
var callAny = CallAny(valueProp, idEqualsLambda);
// x => x.Value.Any(item => item.Id == id)
var firstAny = Expression.Lambda<Func<TestEntity, bool>>(callAny, anyParam);
return NestedFilter(Builders<TestEntity>.Filter.Eq(x => x.Id, id), firstAny, depth);
}
static int paramIndex = 0;
private static FilterDefinition<TestEntity> NestedFilter(FilterDefinition<TestEntity> actual, Expression<Func<TestEntity, bool>> whereExpression, int depth)
{
if (depth == 0)
{
return actual;
}
// paramX
var param = Expression.Parameter(typeof(TestEntity), "param" + paramIndex++);
// paramX.Value
var valueProp = Expression.Property(param, "Value");
// paramX => paramX.Value.Any(...)
var callLambda = Expression.Lambda<Func<TestEntity, bool>>(CallAny(valueProp, whereExpression), param);
return NestedFilter(Builders<TestEntity>.Filter.Where(whereExpression), callLambda, depth - 1) | actual;
}
}
It's still fixed length depth search, but the depth can be varied dinamically. Just 1 step to upgrade the code to try 1st level, 2nd level, .... And the depth is infinite

Find a specific element anywhere in an arbitrary JSON

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

JSON from Adjacency list?

I have an adjacency list like this:
A - A1
A - A2
A - A3
A3 - A31
A31 - A311
A31 - A312
I am trying to obtain the following output:
{
"name": "A",
"children": [{
"name": "A1"
}, {
"name": "A2"
}, {
"name": "A3",
"children": [{
"name": "A31",
"children": [{
"name": "A311"
}, {
"name": "A312"
}]
}]
}]
};
I have a modestly large graph containing 100K links. What is a good way of doing this? I am thinking there is a very elegant recursive way of doing this but am not sure about how to create the JSON string directly.
Something like should work:
static void Main(string[] args)
{
var adjList = new List<Link>
{
new Link("A","A1"),
new Link("A","A2"),
new Link("A","A3"),
new Link("A3","A31"),
new Link("A31","A311"),
new Link("A31","A312"),
};
var rootsAndChildren = adjList.GroupBy(x => x.From)
.ToDictionary(x => x.Key, x => x.Select(y => y.To).ToList());
var roots = rootsAndChildren.Keys
.Except(rootsAndChildren.SelectMany(x => x.Value));
using (var wr = new StreamWriter("C:\\myjson.json"))
{
wr.WriteLine("{");
foreach (var root in roots)
AppendSubNodes(wr, root, rootsAndChildren, 1);
wr.WriteLine("};");
}
}
static void AppendSubNodes(TextWriter wr, string root,
Dictionary<string, List<string>> rootsAndChildren, int level)
{
string indent = string.Concat(Enumerable.Repeat(" ", level * 4));
wr.Write(indent + "\"name\" : \"" + root + "\"");
List<string> children;
if (rootsAndChildren.TryGetValue(root, out children))
{
wr.WriteLine(",");
wr.WriteLine(indent + "\"children\" : [{");
for (int i = 0; i < children.Count; i++)
{
if (i > 0)
wr.WriteLine(indent + "}, {");
AppendSubNodes(wr, children[i], rootsAndChildren, level + 1);
}
wr.WriteLine(indent + "}]");
}
else
{
wr.WriteLine();
}
}
With Link being the following class:
class Link
{
public string From { get; private set; }
public string To { get; private set; }
public Link(string from, string to)
{
this.From = from;
this.To = to;
}
}
Result of the previous code:
{
"name" : "A",
"children" : [{
"name" : "A1"
}, {
"name" : "A2"
}, {
"name" : "A3",
"children" : [{
"name" : "A31",
"children" : [{
"name" : "A311"
}, {
"name" : "A312"
}]
}]
}]
};
EDIT :
If you want to check the existence of graph cycles you can do the following (just after the creation of rootsAndChildren dictionary)
var allNodes = rootsAndChildren.Keys.Concat(rootsAndChildren.SelectMany(x => x.Value)).Distinct();
Func<string, IEnumerable<string>> getSuccessors =
(x) => rootsAndChildren.ContainsKey(x) ? rootsAndChildren[x] : Enumerable.Empty<string>();
var hasCycles = new Tarjan<string>().HasCycle(allNodes, getSuccessors);
With Tarjan being the following class:
// Please note that Tarjan does not detect a cycle due to a node
// pointing to itself. It's pretty trivial to account for that though...
public class Tarjan<T>
{
private class Node
{
public T Value { get; private set; }
public int Index { get; set; }
public int LowLink { get; set; }
public Node(T value)
{
this.Value = value;
this.Index = -1;
this.LowLink = -1;
}
}
private Func<T, IEnumerable<T>> getSuccessors;
private Dictionary<T, Node> nodeMaps;
private int index = 0;
private Stack<Node> stack;
private List<List<Node>> SCC;
public bool HasCycle(IEnumerable<T> nodes, Func<T, IEnumerable<T>> getSuccessors)
{
return ExecuteTarjan(nodes, getSuccessors).Any(x => x.Count > 1);
}
private List<List<Node>> ExecuteTarjan(IEnumerable<T> nodes, Func<T, IEnumerable<T>> getSuccessors)
{
this.nodeMaps = nodes.ToDictionary(x => x, x => new Node(x));
this.getSuccessors = getSuccessors;
SCC = new List<List<Node>>();
stack = new Stack<Node>();
index = 0;
foreach (var node in this.nodeMaps.Values)
{
if (node.Index == -1)
TarjanImpl(node);
}
return SCC;
}
private IEnumerable<Node> GetSuccessors(Node v)
{
return this.getSuccessors(v.Value).Select(x => this.nodeMaps[x]);
}
private List<List<Node>> TarjanImpl(Node v)
{
v.Index = index;
v.LowLink = index;
index++;
stack.Push(v);
foreach (var n in GetSuccessors(v))
{
if (n.Index == -1)
{
TarjanImpl(n);
v.LowLink = Math.Min(v.LowLink, n.LowLink);
}
else if (stack.Contains(n))
{
v.LowLink = Math.Min(v.LowLink, n.Index);
}
}
if (v.LowLink == v.Index)
{
Node n;
List<Node> component = new List<Node>();
do
{
n = stack.Pop();
component.Add(n);
} while (n != v);
SCC.Add(component);
}
return SCC;
}
}

Categories

Resources