关于 c#:从表格分层数据构建 json

Building json from tabular hierarchical data

假设我有一些数据,如下所示:

1
2
3
4
5
6
7
8
9
10
{
   "Menu": {
       "aaa":"aaa",
       "bbb": {
            "ccc":"ccc",
            "ddd":"ddd"
        },
       "eee":"eee"
     }
}

我可以将这种类型的分层数据以这样的关系方式保存到数据库中:

1
http://i.stack.imgur.com/lmuq1.jpg

样品清单:

1
2
3
4
5
6
7
    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 });

因此,当我从 db 获取关系数据作为 MenuItem 对象列表时,如何将其转换回 json?

1
2
3
4
5
6
7
8
9
10
11
12
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; }
}

我必须使用 Dictionary 或 ExpandoObject 之类的吗?我想要与开始时完全相同的格式。


使用 Json.net,我们可以编写一个自定义转换器来从 MenuItem.

列表中生成我们想要的 json

注意:我省略了转换器的读者部分以使其简洁(因为它与问题没有真正相关)但逻辑将类似于作者部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class MenuItemJsonConverter : JsonConverter
{

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof (MenuItemCollection) || objectType==typeof(List<MenuItem>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var map=new Dictionary<int,JObject>();
        var collection = (List<MenuItem>) value;
        var root=new JObject();

        var nestedItems=collection.GroupBy(i => i.ParentId).ToLookup(g=>g.Key); //or we can simply check for item.Url==null but I believe this approach is more flexible

        foreach (var item in collection)
        {
            if (item.ParentId == null)
            {
                var firstObj=new JObject();
                root.Add(item.MenuName,firstObj);
                map.Add(item.SiteMenuId,firstObj);
                continue;
            }

            var parent = map[item.ParentId.Value];

            if (!nestedItems.Contains(item.SiteMenuId))
            {
                parent.Add(item.MenuName,item.Url);
                continue;
            }

            var jObj = new JObject();
            parent.Add(item.MenuName, jObj);
            map.Add(item.SiteMenuId, jObj);
        }

        writer.WriteRaw(root.ToString());
    }
}

这里是直接使用示例:

1
2
3
4
5
6
7
8
9
        var 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 = JsonConvert.SerializeObject(menuItems,Formatting.Indented,new MenuItemJsonConverter());

或者我们可以从 List<> 派生并用 [JsonConverter(typeof(MenuItemJsonConverter))] 装饰它以方便:

1
2
3
4
[JsonConverter(typeof(MenuItemJsonConverter))]
class MenuItemCollection : List<MenuItem>
{      
}

然后简单地使用它:

1
2
3
4
5
6
7
8
9
        var menuItems = new MenuItemCollection();
        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 = JsonConvert.SerializeObject(menuItems,Formatting.Indented);

您可以为此目的创建 KeyValuePair 对象:

1
KeyValuePair<string, List<Object>> toExport = new KeyValuePair<int, int>("Menu", new List<Object>());

然后,您可以添加元素,如下所示:

1
toExport.Value.Add(new KeyValuePair<string, string>("aaa","aaa"));

要为此添加复合内容,您可以执行以下操作:

1
2
3
4
KeyValuePair<string, List<Object>> bbb = new KeyValuePair<string, List<Object>>("bbb", new List<Object>());
bbb.Value.Add(new KeyValuePair<string, string>("ccc","ccc"));
bbb.Value.Add(new KeyValuePair<string, string>("ddd","ddd"));
toExport.Value.Add(bbb);

构建对象后,您可以使用 NewtonSoft\\'s JsonConvert.SerializeObject 方法。

你也可以创建一个辅助类来帮助你。

编辑:创建动态数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class DynamicKeyValueBuilder {

    private KeyValuePair<string, List<Object>> toExport;

    public DynamicKeyValueBuilder(string mainKey) {
        toExport = new KeyValuePair<string, List<Object>>(mainKey, new List<Object>());
    }

    public string getJSON() {
        return JsonConvert.SerializeObject(this.toExport);
    }

    private KeyValuePair<string, List<Object>> searchParent(List<string> path) {
        KeyValuePair<string, List<Object>> temp = (KeyValuePair<string, List<Object>>)this.toExport;
        int index = 0;
        while (index < path.Count) {
            try {
                temp = (KeyValuePair<string, List<Object>>)temp.First(item => item.Key == path.ElementAt(index)); //throws exception if value is not list or the element was not found
                index++;
            } catch (Exception exception) {
                //handle exceptions
                return null;
            }
        }
        return temp;
    }

    //If value == null, we create a list
    public boolean addElement(List<string> path, string key, string value) {
        KeyValuePair<string, Object> parent = this.searchParent(path);
        //failure
        if (parent == null) {
            return false;
        }
        parent.Value.Add((value == null) ? (new KeyValuePair<string, List<Object>>(key, new List<Object>())) : (new KeyValuePair<string, string>(key, value)));
        return true;
    }

}

代码未经测试,如果您遇到错误,请告诉我,而不仅仅是投反对票,我相信我会在这里努力提供帮助。

你可以像这样实例化这个类:

1
DynamicKeyValueBuilder myBuilder = new DynamicKeyValueBuilder("Menu");

当你打算添加一个新的 <string, string> 元素时,你可以这样做:

1
myBuilder.Add(new List<string>(new string[] {"Menu"}),"aaa","aaa");

当你打算添加一个新的 <string, List<Object>> 元素时,你可以这样做:

1
myBuilder.Add(new List<string>(new string[] {"Menu"}),"bbb", null);

当你打算在内部列表中添加一些东西时,你可以这样做:

1
myBuilder.Add(new List<string>(new string[] {"Menu","bbb"}),"ccc","ccc");


如果您可以使用 NewtonSoft,请使用反序列化您的 JSON 内容并查看结果对象的外观。然后,创建一个与反序列化结果结构匹配的类。逆向工程...

使用这个方法:

1
var obj = JsonConvert.DeserializeObject("{"menu": {"aaa":"aaa"......} }");

让我知道你的发现。