As in the example you linked to, there are two main tasks. First, we need to get the data from the dictionary into a hierarchical form. Once, we've done that, we can worry about serializing it to JSON.
So first thing, we need a Node
class to represent the hierarchy:
class Node
{
public Node()
{
Children = new List<Node>();
}
public string Name { get; set; }
public List<Node> Children { get; set; }
}
Once we have that, we can go through the dictionary and build the tree. (Note: in your desired JSON, you show Paint bedroom
and Other
as subordinate to Remodeling
, while in your example dictionary data they are subordinate to Shopping
. I am assuming the JSON is correct in this case, so I changed the dictionary data accordingly as shown below.)
Dictionary<string, string> dict = new Dictionary<string, string>();
dict.Add("Kitchen supplies", "Shopping / Housewares");
dict.Add("Groceries", "Shopping / Housewares");
dict.Add("Cleaning supplies", "Shopping / Housewares");
dict.Add("Office supplies", "Shopping / Housewares");
dict.Add("Retile kitchen", "Shopping / Remodeling");
dict.Add("Ceiling", "Shopping / Remodeling / Paint bedroom");
dict.Add("Walls", "Shopping / Remodeling / Paint bedroom");
dict.Add("Misc", null);
dict.Add("Other", "Shopping / Remodeling");
Node root = new Node();
foreach (KeyValuePair<string, string> kvp in dict)
{
Node parent = root;
if (!string.IsNullOrEmpty(kvp.Value))
{
Node child = null;
foreach (string part in kvp.Value.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries))
{
string name = part.Trim();
child = parent.Children.Find(n => n.Name == name);
if (child == null)
{
child = new Node { Name = name };
parent.Children.Add(child);
}
parent = child;
}
}
parent.Children.Add(new Node { Name = kvp.Key });
}
Now that we have our tree, we can serialize it. However, we need some special handling because your leaf nodes are rendered differently than non-leaf nodes in your JSON: leaf nodes have a leaf
property and no children
property, while the reverse is true for non-leaf nodes. To handle this logic, we will need a custom JsonConverter
. (Just to clarify, I am using Json.Net here-- your question did not mention a specific JSON serializer.)
class NodeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Node));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Node node = (Node)value;
JObject jo = new JObject();
jo.Add("name", node.Name);
if (node.Children.Count == 0)
{
jo.Add("leaf", true);
}
else
{
jo.Add("children", JArray.FromObject(node.Children, serializer));
}
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
We can use the JsonConverter to serialize the tree to JSON like this:
JsonSerializerSettings settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new NodeConverter() },
Formatting = Formatting.Indented
};
string json = JsonConvert.SerializeObject(root, settings);
Console.WriteLine(json);
Here is the output:
{
"name": ".",
"children": [
{
"name": "Shopping",
"children": [
{
"name": "Housewares",
"children": [
{
"name": "Kitchen supplies",
"leaf": true
},
{
"name": "Groceries",
"leaf": true
},
{
"name": "Cleaning supplies",
"leaf": true
},
{
"name": "Office supplies",
"leaf": true
}
]
},
{
"name": "Remodeling",
"children": [
{
"name": "Retile kitchen",
"leaf": true
},
{
"name": "Paint bedroom",
"children": [
{
"name": "Ceiling",
"leaf": true
},
{
"name": "Walls",
"leaf": true
}
]
},
{
"name": "Other",
"leaf": true
}
]
}
]
},
{
"name": "Misc",
"leaf": true
}
]
}
One other minor note: in your desired JSON above, you show the root node with a text
property instead of a name
property, which is inconsistent with all the other nodes. I am assuming this was a mistake. If it wasn't, you'll need to change the JsonConverter so that it has logic to output a text
property in place of the name
if the name is a dot (.
).
Hope this helps.