Группировка плоских данных для создания иерархического дерева с использованием LINQ для JSON

#c# #linq

Вопрос:

Я пытаюсь спроецировать плоский источник данных в объект, который может быть сериализован непосредственно в JSON с помощью Newtonsoft.Json. Я создал небольшую программу в Linqpad с воображаемым обзором запасов в качестве теста. Запрошенный результат выглядит следующим образом:

  • Название сайта
    • Инвентарное наименование
      • Название продукта
      • Вес
      • Единицы
    • Инвентарное наименование
      • Продукт (и т. Д.)

Ни за что на свете я не могу заставить его иметь только одно «Имя сайта» в качестве единственного корневого объекта. Я хочу, чтобы список содержимого внутри инвентаря внутри сайта, но он всегда выглядит так: введите описание изображения здесь

Как я могу отличить «Сайт» от коллекции «инвентаря», у каждого из которых есть коллекция «продуктов»?

Мой фактический источник данных — это таблица базы данных, и она напоминает структуру моего тестового объекта-и это то, что она есть.

Тестовый код в Linqpad: (обратите внимание, что он ссылается на Newtonsoft.Json)

 void Main()
{
    var contents = new List<DatabaseRecord>()
    {
        new DatabaseRecord{Product="Autoblaster", Inventory="Hull 5", Site="Death star", Units=20,Weight=500},
        new DatabaseRecord{Product="E11 Blaster Rifle", Inventory="Hull 5", Site="Death star", Units=512,Weight=4096},
        new DatabaseRecord{Product="SWE/2 Sonic Rifle", Inventory="Hull 1", Site="Death star", Units=20,Weight=500},
        new DatabaseRecord{Product="Relby v10 Micro Grenade Launcher", Inventory="Hull 5", Site="Death star", Units=20,Weight=500},
        new DatabaseRecord{Product="T-8 Disruptor", Inventory="Hull 1", Site="Death star", Units=20,Weight=500},
        new DatabaseRecord{Product="E11 Blaster Rifle", Inventory="Hull 2", Site="Death star", Units=50,Weight=1200}
    };
    
    var inventorycontent = from row in contents
                    group row by row.Site into sites
                    orderby sites.Key
                    select from inventory in sites
                        group inventory by inventory.Inventory into inventories
                           orderby inventories.Key
                           select new
                           {
                               site = sites.Key,
                               inventory = inventories.Key,
                               lines = inventories.Select(i => new { i.Product, i.Weight, i.Units })
                           };

    contents.Dump();
    inventorycontent.Dump();
    
    JsonConvert.SerializeObject(inventorycontent, Newtonsoft.Json.Formatting.Indented).Dump();
}

// Define other methods and classes here
class DatabaseRecord
{
    public string Product { get; set; }
    public string Inventory { get; set; }
    public string Site { get; set; }
    public int Units { get; set; }
    public double Weight { get; set; }
    
}
 

Вывод JSON:

 [
  [
    {
      "site": "Death star",
      "inventory": "Hull 1",
      "lines": [
        {
          "Product": "SWE/2 Sonic Rifle",
          "Weight": 500.0,
          "Units": 20
        },
        {
          "Product": "T-8 Disruptor",
          "Weight": 500.0,
          "Units": 20
        }
      ]
    },
    {
      "site": "Death star",
      "inventory": "Hull 2",
      "lines": [
        {
          "Product": "E11 Blaster Rifle",
          "Weight": 1200.0,
          "Units": 50
        }
      ]
    },
    {
      "site": "Death star",
      "inventory": "Hull 5",
      "lines": [
        {
          "Product": "Autoblaster",
          "Weight": 500.0,
          "Units": 20
        },
        {
          "Product": "E11 Blaster Rifle",
          "Weight": 4096.0,
          "Units": 512
        },
        {
          "Product": "Relby v10 Micro Grenade Launcher",
          "Weight": 500.0,
          "Units": 20
        }
      ]
    }
  ]
]
 

Предложенный правильный образец вывода:

 {
  "sites":[{
    "site": "Death star",
    "inventories":[
      {
        "name":"Hull 1",
        "lines":[{
          "Product": "SWE/2 Sonic Rifle",
          "Weight": 500.0,
          "Units": 20
        },
        {
          "Product": "T-8 Disruptor",
          "Weight": 500.0,
          "Units": 20
        }]
      },
      {
        "name":"Hull 2",
        "lines":[{
          "Product": "SWE/2 Sonic Rifle",
          "Weight": 500.0,
          "Units": 20
        }]
      }
      ]
    },
    {"site": "Other site",
    "inventories":[
      {
        "name":"Hull 1",
        "lines":[{
          "Product": "SWE/2 Sonic Rifle",
          "Weight": 500.0,
          "Units": 20
        }]
      }]
    }]
}
 

Комментарии:

1. Вместо использования анонимного типа и группировок… Я бы использовал группировки и словари.

2. Можете ли вы показать правильный образец вывода JSON?

3. @NetMage Добавил пример вывода JSON

4. Я немного изменил свой ответ, чтобы соответствовать вашему выходу json

Ответ №1:

Хорошо, у меня есть решение с использованием словарей, которое все правильно сгруппирует:

            //first get everything properly grouped with dictionaries
           var result = contents
            .GroupBy(x => x.Site)
            .ToDictionary(g => g.Key, g => g
                                .GroupBy(i => i.Inventory)
                                .ToDictionary(i => i.Key, i => i
                                        .Select(a => new 
                                            { 
                                                Product = a.Product, 
                                                Weight = a.Weight, 
                                                Units = a.Units 
                                            })
                                            .ToList()));

            //project to a new object that matches your desired json  
            var formattedResult = new
            {
                sites = (from r in result
                         select new
                         {
                             site = r.Key,
                             inventories = (from i in r.Value select new { name = i.Key, lines = i.Value }).ToList()
                         }).ToList()
            };
 

Это выходной json:

 {
   "sites": [
    {
      "site": "Death star",
      "inventories": [
        {
          "name": "Hull 5",
          "lines": [
            {
              "Product": "Autoblaster",
              "Weight": 500.0,
              "Units": 20
            },
            {
              "Product": "E11 Blaster Rifle",
              "Weight": 4096.0,
              "Units": 512
            },
            {
              "Product": "Relby v10 Micro Grenade Launcher",
              "Weight": 500.0,
              "Units": 20
            }
          ]
        },
        {
          "name": "Hull 1",
          "lines": [
            {
              "Product": "SWE/2 Sonic Rifle",
              "Weight": 500.0,
              "Units": 20
            },
            {
              "Product": "T-8 Disruptor",
              "Weight": 500.0,
              "Units": 20
            }
          ]
        },
        {
          "name": "Hull 2",
          "lines": [
            {
              "Product": "E11 Blaster Rifle",
              "Weight": 1200.0,
              "Units": 50
            }
          ]
        }
      ]
    }
  ]
}
 

Как вы можете видеть, я группирую по сайту, затем делаю это в словаре, который затем группирую по инвентарю и делаю это в другом словаре со списком продуктов в качестве значений.

Таким образом, в основном результатом является

  Dictionary<string, Dictionary<string, List<Products>>>
 

Ключами словаря являются Сайт, Название инвентаря.

Я использую этот словарь GroupBy — > > > каждый раз, когда мне приходится создавать иерархический json, подобный этому.

Комментарии:

1. Большое вам спасибо! Это отличное решение, я никогда не рассматривал возможность использования словарей для этого. Однако, что делать, если вы хотите явно назвать значения? Итак, вместо «Звезды смерти»:{ (…) } вам нужно: «Сайт»: {«Имя»: «Звезда смерти», «Описи» :[ (…) ] и т.д. Другими словами, более тесно связано с выводом, который я имею в своем вопросе? Кроме того, в вашем примере есть небольшая ошибка: при последнем выборе используется i , но это имя уже используется в этой области. Я изменил его на а , и все работало нормально.

2. @GeirR Я добавил небольшой фрагмент кода и обновленный ввод json. Я просто спроецировал словари в объект, соответствующий вашему желаемому json.