1

I have a list as you see below:

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

Is it possible to translate this data structure to a format that can be serialized as the following json:

{
    "Menu": {
        "aaa": "aaa",
        "bbb": {
            "ccc": "ccc",
            "ddd": "ddd"
        },
        "eee": "eee"
    }
}

If the answer is yes, how can I do that?

xkcd
  • 2,538
  • 11
  • 59
  • 96
  • Perhaps you could suuply some sample code and rephrase your question as "Here's what I tried why doesn't it work?". This is a "Give me the codez" question that isn't partiularly suitable for Stack Overflow. – Paul Hicks Aug 23 '15 at 00:19
  • @PaulHicks Did you read the answer/s for this question? You know that there is a generic response to these kinds of questions, But if you read my comments for the answer you will see that this is not my expectation. Anyway thanks for your effort for writing this kind of helpfull comment. – xkcd Aug 23 '15 at 00:31
  • Yes, there are [generic answers](http://stackoverflow.com/help/dont-ask) that are very suitable for tutorial websites, Yahoo! Answers, and other places. This site prefers [specific questions with specific answers](http://stackoverflow.com/help/on-topic). – Paul Hicks Aug 23 '15 at 03:35

2 Answers2

1

You can try to Create a class like this

public class MenuItemTr
{

 public MenuItemTr
 {
    this.MenuItems= new List <MenuItem>
 }
public int SiteMenuId {get; set;}
public int ParentId {get; set;}
public string MenuName {get; set;}
public string Url {get; set;}
public int SiteId {get; set;}
public List <MenuItemTr> MenuItems {get; set;}
}

and then parse it to a tree

var MenuItem = menuItems.GenerateTree(c => c.SiteMenuId, c => c.ParentId);

and use this solution from this thread Nice & universal way to convert List of items to Tree

public static IEnumerable<TreeItem<T>> GenerateTree<T, K>(
        this IEnumerable<T> collection,
        Func<T, K> id_selector,
        Func<T, K> parent_id_selector,
        K root_id = default(K))
    {
        foreach (var c in collection.Where(c => parent_id_selector(c).Equals(root_id)))
        {
            yield return new TreeItem<T>
            {
                Item = c,
                Children = collection.GenerateTree(id_selector, parent_id_selector, id_selector(c))
            };
        }
    }
}
Community
  • 1
  • 1
COLD TOLD
  • 13,513
  • 3
  • 35
  • 52
  • But this object will not be serialized as expected in the question? – xkcd Aug 20 '15 at 23:15
  • I need a json transformation to the same format that explained in question. By serializing the MenuItemTr object you will have MenuItems:[] part in json. – xkcd Aug 22 '15 at 13:25
1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340