You could consider using Json.NET.
Json.NET serializes any IDictionary
to a JSON key/value pair object - but converting to a Dictionary<string, object>
then serializing would be problematic because the .Net dictionary is unordered, and you (probably) want to preserve the relative order of the MenuItem
objects when serializing to JSON. Thus it makes sense to manually convert to a tree of JObject
objects using LINQ to JSON since Json.NET preserves order of object properties.
Thus you would do:
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();
}
(Note this method throws an exception if a MenuItem
has both a non-null Url
and a collection of children.)
It uses the following extension methods:
public static class JsonExtensions
{
public static void MoveTo(this JToken token, JObject newParent)
{
if (newParent == null)
throw new ArgumentNullException();
var toMove = token.AncestorsAndSelf().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)]);
}
}
Working fiddle.