JContainer、 JObject、 JToken 和 Linq 混淆

我有麻烦了解什么时候使用 JContainerJObjectJToken。我从“标准”中了解到,JObject是由 JProperties组成的,而 JToken是所有 JToken类型的基础抽象类,但我不了解 JContainer

我正在使用 C # ,我刚刚购买了 LinqPad Pro 5。

我在一个文件中有一个 JSON 数据源,因此我正在使用以下语句成功地反序列化该文件的内容:

string json;
using (StreamReader reader = new StreamReader(@"myjsonfile.json"))
{
json = reader.ReadToEnd();
}

此时,我将 JSON 字符串对象反序列化为 JObject(这可能是我的错误——也许我需要将 jsonWork设置为 JTokenJContainer?):

JObject jsonWork = (JObject)JsonConvert.DeserializeObject(json);

在我的 JSON 数据(由 JSON 表示的字符串)中,我有三个对象——顶级对象看起来类似于:

{
"Object1" : { ... },
"Object2" : { ... },
"Object3" : { ... }
}

每个对象都由各种标记(数组、字符串、其他对象等)组成,因此它是动态 JSON。(我使用省略号作为占位符,而不是用大量 JSON 数据混淆这个问题。)

但是,我想使用 LINQ 分别处理 "Object1""Object2""Object3":

// these lines DO NOT work
var jsonObject1 = jsonWork.Children()["Object1"]
var jsonObject2 = jsonWork.Children()["Object2"]
var jsonObject3 = jsonWork.Children()["Object3"]

但是以上几行都失败了。

我在上面使用了 var,因为我不知道我应该使用什么对象类型: JContainerJObject或者 JToken!为了让您知道我想做什么,一旦上面的 jsonObject#变量被正确地分配,我想使用 LINQ 来查询它们包含的 JSON。下面是一个非常简单的例子:

var query = from p in jsonObject1
where p.Name == "Name1"
select p

当然,我的 LINQ 最终将在 jsonObject变量中筛选 JSON 数组、对象、字符串等。我认为一旦我开始了,我可以使用 LinqPad 来帮助我使用 LINQ 过滤 JSON。

我发现如果我使用:

// this line WORKS
var jsonObject1 = ((JObject)jsonWork).["Object1"];

然后我在 jsonObject1中得到一个 JObject类型。这是正确的方法吗?

JTokenJObject对象与 LINQ 工作得很好时,我不清楚什么时候/为什么要使用 JContainerJContainer的目的是什么?

69226 次浏览

JContainer is a base class for JSON elements that have child items. JObject, JArray, JProperty and JConstructor all inherit from it.

For example, the following code:

(JObject)JsonConvert.DeserializeObject("[1, 2, 3]")

Would throw an InvalidCastException, but if you cast it to a JContainer, it would be fine.

Regarding your original question, if you know you have a JSON object at the top level, you can just use:

var jsonWork = JObject.Parse(json);
var jsonObject1 = jsonWork["Object1"];

You don't really need to worry about JContainer in most cases. It is there to help organize and structure LINQ-to-JSON into well-factored code.

The JToken hierarchy looks like this:

JToken             - abstract base class
JContainer      - abstract base class of JTokens that can contain other JTokens
JArray      - represents a JSON array (contains an ordered list of JTokens)
JObject     - represents a JSON object (contains a collection of JProperties)
JProperty   - represents a JSON property (a name/JToken pair inside a JObject)
JValue          - represents a primitive JSON value (string, number, boolean, null)

So you see, a JObject is a JContainer, which is a JToken.

Here's the basic rule of thumb:

  • If you know you have an object (denoted by curly braces { and } in JSON), use JObject
  • If you know you have an array or list (denoted by square brackets [ and ]), use JArray
  • If you know you have a primitive value, use JValue
  • If you don't know what kind of token you have, or want to be able to handle any of the above in a general way, use JToken. You can then check its Type property to determine what kind of token it is and cast it appropriately.

Most examples have simple json and I've googled "C# Newtonsoft parse JSON" more than once.

Here's a bit of a json file I was just asked to parse for a csv. The company name value is nested within many arrays / objects so it is semi-complicated in that regard.

{
"page": {
"page": 1,
"pageSize": 250
},
"dataRows": [
{
"columnValues": {
"companyName": [
{
"name": "My Awesome Company",
}
]
}
}
]
}
            var jsonFilePath = @"C:\data.json";
var jsonStr = File.ReadAllText(jsonFilePath);


// JObject implementation for getting dataRows JArray - in this case I find it simpler and more readable to use a dynamic cast (below)
//JObject jsonObj = JsonConvert.DeserializeObject<JObject>(jsonStr);
//var dataRows = (JArray)jsonObj["dataRows"];


var dataRows = ((dynamic)JsonConvert.DeserializeObject(jsonStr)).dataRows;


var csvLines = new List<string>();


for (var i = 0; i < dataRows.Count; i++)
{
var name = dataRows[i]["columnValues"]["companyName"][0]["name"].ToString();


// dynamic casting implemntation to get name - in this case, using JObject indexing (above) seems easier
//var name2 = ((dynamic)((dynamic)((dynamic)dataRows[i]).columnValues).companyName[0]).name.ToString();


csvLines.Add(name);
}


File.WriteAllLines($@"C:\data_{DateTime.Now.Ticks}.csv", csvLines);