【问题标题】:Building json from tabular hierarchical data从表格分层数据构建 json
【发布时间】:2015-11-14 06:57:01
【问题描述】:

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

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

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

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

示例列表:

    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 对象的 List 时,如何将其翻译回 json?

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 吗?我希望拥有与开始时完全相同的格式。

【问题讨论】:

  • 查找 newtonsoft - newtonsoft.com/json 以及如何序列化为 json。
  • 谢谢 我知道 newtonsoft。但我希望我的 json 与第一种格式相同。这实际上是我要问的。
  • 我通常使用 Automapper 在通过 http 和 dto 公开的模型之间进行转换。
  • @BrianRogers 是的,但我不想在我的 json 格式中有 children:[] 属性。我需要具有与我的问题中解释的完全相同的格式。

标签: c# json serialization dynamic expandoobject


【解决方案1】:

使用Json.net,我们可以编写一个自定义转换器,从MenuItem列表中生成我们想要的json。

注意:我省略了转换器的阅读器部分以使其简洁(因为它与问题并不真正相关),但逻辑将类似于编写器部分。

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

这里是直接使用示例:

        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&lt;&gt; 派生并用[JsonConverter(typeof(MenuItemJsonConverter))] 装饰它以更方便:

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

然后简单地使用它:

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

【讨论】:

    【解决方案2】:

    您可以为此创建KeyValuePair 对象:

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

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

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

    要向其中添加复合内容,您可以执行以下操作:

    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 的JsonConvert.SerializeObject 方法。

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

    编辑:动态数据的创建。

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

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

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

    DynamicKeyValueBuilder myBuilder = new DynamicKeyValueBuilder("Menu");
    

    当你打算添加一个新的&lt;string, string&gt; 元素时,你可以这样做:

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

    当你打算添加一个新的&lt;string, List&lt;Object&gt;&gt; 元素时,你可以这样做:

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

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

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

    【讨论】:

    • 谢谢,看来 KeyValuePair 对象会帮助我。但我的主要问题是如何使用动态数据构建适当的对象。想想 json 中的项目数量和它们的深度会有所不同。
    • @anilca,我已经编辑了我的答案。代码未经测试,如果您发现其中有问题,请告诉我。
    • 其实我不懂你们的builder怎么用。我将项目示例列表添加到问题中。如果您有示例列表,您如何使用它来创建我期望的 json?如果我有 2 个根元素,而不是示例中的“菜单”,该怎么办?
    • 我相信您的问题是您没有成功构建预期作为路径的列表。您有一个 ParentId 可以帮助您完成任务。例如,5 的父节点是 3,3 的父节点是 1,1 的父节点是 null,所以 1 是根。例如,您可以使用 ParentId 关系构建树。最初没有提到多个根,但您可以使用 List 来达到此目的,或者您可以制作 toExport 列表并稍微修改 searchParent。
    【解决方案3】:

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

    使用这个方法:

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

    让我知道你的发现。

    【讨论】:

    • 但是我的 json 每次都不一样。想想 json 中的 item 的数量和它们的深度会有所不同
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 2012-04-23
    • 2013-08-04
    • 1970-01-01
    相关资源
    最近更新 更多