CSharp · Console
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public static class JsonExtensions
{
public static IEnumerable<JToken> AncestorsAndSelf(this JToken token) // dotnetfiddle.net seems to be loading Json.NET 6.0 not 7.0. This extension method is not needed in 7.0
{
for (JToken current = token; current != null; current = current.Parent)
{
yield return current;
}
}
public static void MoveTo(this JToken token, JObject newParent)
{
if (newParent == null)
throw new ArgumentNullException();
var toMove = AncestorsAndSelf(token).OfType<JProperty>().First(); // Throws an exception if no parent property found.
if (toMove.Parent != null)
toMove.Remove();
newParent.Add(toMove);
}
}
public static class RecursiveEnumerableExtensions
{
static bool ContainsNonNullKey<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
{
if (dictionary == null)
throw new ArgumentNullException();
return key == null ? false : dictionary.ContainsKey(key); // Dictionary<int?, X> throws on ContainsKey(null, ...)
}
public static IEnumerable<TResult> ToTree<TInput, TKey, TResult>(
this IEnumerable<TInput> collection,
Func<TInput, TKey> idSelector,
Func<TInput, TKey> parentIdSelector,
Func<TInput, TResult> nodeSelector,
Action<TResult, TResult> addMethod)
{
if (collection == null || idSelector == null || parentIdSelector == null || nodeSelector == null || addMethod == null)
throw new ArgumentNullException();
var list = collection.ToList(); // Prevent multiple enumerations of the incoming enumerable.
var dict = list.ToDictionary(i => idSelector(i), i => nodeSelector(i));
foreach (var input in list.Where(i => dict.ContainsNonNullKey(parentIdSelector(i))))
{
addMethod(dict[parentIdSelector(input)], dict[idSelector(input)]);
}
return list.Where(i => !dict.ContainsNonNullKey(parentIdSelector(i))).Select(i => dict[idSelector(i)]);
}
}
public partial class MenuItem
{
public int SiteMenuId { get; set; }
public int SiteId { get; set; }
public string MenuName { get; set; }
public string Url { get; set; }
public Nullable<int> ParentId { get; set; }
public int CreatedUser { get; set; }
public System.DateTime CreatedDate { get; set; }
public Nullable<int> ModifiedUser { get; set; }
public Nullable<System.DateTime> ModifiedDate { get; set; }
}
public class TestClass
{
public static void Test()
{
var desiredJson = @"{
""Menu"": {
""aaa"": ""aaa"",
""bbb"": {
""ccc"": ""ccc"",
""ddd"": ""ddd""
},
""eee"": ""eee""
}
}";
List<MenuItem> menuItems = new List<MenuItem>();
menuItems.Add(new MenuItem() { SiteMenuId = 1, ParentId = null, MenuName = "Menu", Url = null, SiteId = 1 });
menuItems.Add(new MenuItem() { SiteMenuId = 2, ParentId = 1, MenuName = "aaa", Url = "aaa", SiteId = 1 });
menuItems.Add(new MenuItem() { SiteMenuId = 3, ParentId = 1, MenuName = "bbb", Url = null, SiteId = 1 });
menuItems.Add(new MenuItem() { SiteMenuId = 4, ParentId = 3, MenuName = "ccc", Url = "ccc", SiteId = 1 });
menuItems.Add(new MenuItem() { SiteMenuId = 5, ParentId = 3, MenuName = "ddd", Url = "ddd", SiteId = 1 });
menuItems.Add(new MenuItem() { SiteMenuId = 6, ParentId = 1, MenuName = "eee", Url = "eee", SiteId = 1 });
var json = CreateJsonFromMenuItems(menuItems);
Console.WriteLine(json);
if (!JToken.DeepEquals(JToken.Parse(json), JToken.Parse(desiredJson)))
throw new InvalidOperationException();
}
public static string CreateJsonFromMenuItems(IList<MenuItem> menuItems)
{
return new JObject
(
menuItems.ToTree(
m => (int?)m.SiteMenuId,
m => m.ParentId, m => new JProperty(m.MenuName, m.Url),
(parent, child) =>
{
if (parent.Value == null || parent.Value.Type == JTokenType.Null)
parent.Value = new JObject();
else if (parent.Value.Type != JTokenType.Object)
throw new InvalidOperationException("MenuItem has both URL and children");
child.MoveTo((JObject)parent.Value);
})
).ToString();
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(typeof(JToken).Assembly.FullName);
Console.WriteLine("");
TestClass.Test();
}
}