如何在 C# 中正确连接两个 Json 文件?

Posted

技术标签:

【中文标题】如何在 C# 中正确连接两个 Json 文件?【英文标题】:How to Right Join two Json files in C#? 【发布时间】:2021-08-18 19:28:24 【问题描述】:

我想用一个公共键连接两个 json 文件,并从右侧文件中获取所有记录并从左侧获取匹配数据。

如果是 SQL。

SELECT json1.CategoryDescription, json2.CategoryID, json2.TechName, json2.SpawnID
FROM json1
RIGHT JOIN json2
ON json1.CategoryID = json2.CategoryID
WHERE GameVersion = "A" OR GameVersoion = "2" AND CategoryID = "metals"

我需要获取所有 json2 记录和每个记录的 json1.CategoryDe​​scription。但目前它只列出了 json1 中的所有记录,然后列出了 json2 中的所有记录。

这是我目前的尝试:

using System;
using System.IO;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

namespace ConsoleApp1

    public class Program
    
        public static void Main()
        
            // Filter variables
            var gameVer = "2";
            var cat = "metals";

            // Load the categories.json
            JObject catObj = JObject.Load(new JsonTextReader(File.OpenText("D:/Code/Tests/categories.json")));
            // Load the techtype.json
            JObject ttObj = JObject.Load(new JsonTextReader(File.OpenText("D:/Code/Tests/techtypes.json")));
            // Read techtype.json into an array

            var mergeSettings = new JsonMergeSettings
            
                MergeArrayHandling = MergeArrayHandling.Union
            ;
            catObj.Merge(ttObj, mergeSettings);

            // Does not work,
            /*
             Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
            at ConsoleApp1.Program.Main() in D:\Code\Tests\ReadTechTypes\ReadTechTypes\Program.cs:line 30
            */
            // (catObj.SelectToken("Categoris") as JArray).Merge(ttObj.SelectToken("TechType"), mergeSettings);

            // Does not work, same error
            //var mergedArray = catObj.SelectToken("Categoris") as JArray;
            //string json = mergedArray.ToString();

            Console.WriteLine(catObj);
        
    

左边的json


   "Categories":[
      
         "CategoryID":"baseupgrades",
         "CategoryDescription":"Base Upgrades",
         "IncludeCategory":true,
         "GameVersion":"A"
      ,
      
         "CategoryID":"batteries",
         "CategoryDescription":"Batteries",
         "IncludeCategory":true,
         "GameVersion":"A"
      ,
      
         "CategoryID":"blueprint",
         "CategoryDescription":"Blueprint",
         "IncludeCategory":false,
         "GameVersion":"A"
      
      // Other category values omitted
   ]

正确的json


   "Items":[
      
         "CategoryID":"crystalline",
         "TechName":"Quartz",
         "SpawnID":"quartz",
         "TechID":1,
         "GameVersion":"A"
      ,
      
         "CategoryID":"metals",
         "TechName":"Metal Salvage",
         "SpawnID":"scrapmetal",
         "TechID":2,
         "GameVersion":"A"
      ,
      
         "CategoryID":"outcrop",
         "TechName":"Limestone Outcrop",
         "SpawnID":"limestonechunk",
         "TechID":4,
         "GameVersion":"A"
      
      // Other items omitted
   ]

有什么想法吗?

【问题讨论】:

将两个 json 转换为集合并加入? 【参考方案1】:

你可以试试这个

categoriesRoot = JsonConvert.DeserializeObject<CategoriesRoot>(categoriesJson);
itemsRoot = JsonConvert.DeserializeObject<ItemsRoot>(itemsJson);


var items = from cr in categoriesRoot.Categories
    join ir in itemsRoot.Items on cr.CategoryID equals ir.CategoryID into irj
    from ir in irj.DefaultIfEmpty()
    where ( (cr.GameVersion == "A") || (cr.GameVersion == "2" && cr.CategoryID == "metals"))
                 select new  
                  cr.CategoryDescription,
                  ir.CategoryID,
                  ir.TechName,
                  ir.SpawnID
    ;

var newItemsJson=JsonConvert.SerializeObject(items);

创建这些类之后

public class Item

    public string CategoryID  get; set; 
    public string TechName  get; set; 
    public string SpawnID  get; set; 
    public int TechID  get; set; 
    public string GameVersion  get; set; 


public class ItemsRoot

    public List<Item> Items  get; set; 



public class Category

    public string CategoryID  get; set; 
    public string CategoryDescription  get; set; 
    public bool IncludeCategory  get; set; 
    public string GameVersion  get; set; 


public class CategoriesRoot

    public List<Category> Categories  get; set; 

输出会是这样的

[
"CategoryDescription":"Base Upgrades","CategoryID":"crystalline","TechName":"Quartz","SpawnID":"quartz",
"CategoryDescription":"Batteries","CategoryID":"metals","TechName":"Metal Salvage","SpawnID":"scrapmetal"
]

顺便说一句,您的 SQL 查询中有一个错误

WHERE GameVersion = "A" OR GameVersoion = "2" AND CategoryID = "metals"

这是一个模棱两可的代码,因为两个查询中都有 GameVersion 和 CategoryID。

【讨论】:

1) 我从 Console.WriteLine(items) 得到这个输出;而且我太缺乏经验,无法理解。请参阅下一条评论。 System.Linq.Enumerable+WhereSelectEnumerableIterator2[f__AnonymousType12[f__AnonymousType02[ConsoleApp1.Program+Category,System.Collections.Generic.IEnumerable1[ConsoleApp1.Program+Item]],ConsoleApp1.Program+项目],f__AnonymousType2`4[System.String,System.String,System.String,System.String]] 2) 是的,我确信 SQL 中有一些错误,我主要是想传达所需连接的想法。 :)【参考方案2】:

以下应该有效:

// Filter variables
var gameVersions = new HashSet<string>  "A", "2" ;
var categoryIDs = new HashSet<string>  "metals" ;

// Left outer join on ttObj.  Select all Items[*] array items
var query = from i in ttObj.SelectTokens("Items[*]").OfType<JObject>()
    // Filter on the game version and category ID
    let categoryId = (string)i["CategoryID"]           
    let gameVersion = (string)i["GameVersion"]
    where categoryIDs.Contains(categoryId) && gameVersions.Contains(gameVersion)
    // Join with "Categories[*]" on category ID
    join c in catObj.SelectTokens("Categories[*]") on categoryId equals (string)c["CategoryID"] into joined
    // DefaultIfEmpty makes this a left join
    from cat in joined.DefaultIfEmpty()               
    // Select all records of i and add the CategoryDescription from cat.
    select new JObject(i.Properties())  new JProperty("CategoryDescription", cat?["CategoryDescription"]) ;

var results = query.ToList(); // Materialize the query into a list of results.

结果:

[
  
    "CategoryID": "metals",
    "TechName": "Metal Salvage",
    "SpawnID": "scrapmetal",
    "TechID": 2,
    "GameVersion": "A",
    "CategoryDescription": null
  
]

注意事项:

我将查询从右连接更改为左连接,因为它使过滤看起来更自然一些。如果您希望使用正确的连接语法,请参阅 LINQ Left Join And Right Join

最后的select 语句创建一个新的JObject,其中包含来自JObject i 项目对象的所有记录,然后添加来自cat 类别对象的CategoryDescription。它不会修改现有对象i

JContainer.Merge() 在这里不会为您提供帮助,因为它没有任何基于某些主键进行合并的能力。

ttObj.SelectTokens("Items[*]") 使用JSONPath wildcard operator [*] 选择"Items" 数组中的所有项目。

由于没有"CategoryID":"metals" 的类别,cat 在最终的select 语句中为空。

演示小提琴here.

【讨论】:

我创建了一个新问题,但也许从这里问会更容易。我需要过滤 (bool)objCat.["IncludeCategory"] == true。我尝试了很多方法,但都被 IntelliSense 拒绝或在运行时生成一个或 null 集。【参考方案3】:

问题是您将“类别”列表与“项目”列表合并,而“项目”在 catObj 上不存在。

[我建议你转换类中的项目(使用visual studio你可以做一个“特殊粘贴”作为JSON类)。]

您必须遍历第一个列表的项目并与第二个列表中的相应元素合并,成员与成员,而不是列表与列表。

【讨论】:

我怀疑你是对的。我找到的例子都是合并的,我还没有足够的经验。感谢您的帮助。

以上是关于如何在 C# 中正确连接两个 Json 文件?的主要内容,如果未能解决你的问题,请参考以下文章

在c#中正确创建一个json文件

如何通过 Microsoft Azure Query 从 json 文件中的数组中获取数据

如何在 D3 中正确加载本地 JSON?

如何使用一行代码在 C# 中连接两个数组

如何在 C# 中编写 JSON 文件?

如何在json单引号中写入c#字符串