在 C# 中使用动态嵌套 JSON 作为值迭代 IDictionary<string, string>
Iterate IDictionary<string, string> with dynamic nested JSON as value in C#
我的应用程序收到包含 Dictionary<string,string>
作为属性之一的 Kafka 消息,它的值可能是一个嵌套的(但是动态的)json 字符串,我需要遍历这个未知的json。我正在努力寻找一种逻辑,甚至是最好的数据结构来进行这次迭代。
字典示例(模拟数据):
//could have complex nested json string as value
"reward":"{
'xp':'200',
'gp':'150',
'loot':'{
'item':'sword',
'rarity': 'low'
}'
}",
"achievement":"win_match"
// while other messages might be simple
"type":"generic_xp",
"percent":"100",
"status":"complete"
真实消息的序列化版本:
"{\"player_stats\":\"{\\"assist\\":0,\\"deaths\\":0,\\"kills\\":0,\\"team_index\\":2}\",\"round_attr\":\"{\\"max_player_count\\":4,\\"rdur\\":0,\\"round\\":1,\\"team_player_count\\":{\\"team_1\\":1,\\"team_2\\":0},\\"team_score\\":0}\",\"custom\":\"{\\"armor\\":\\"armor_pickup_lv2\\",\\"balance\\":550,\\"helmet\\":\\"helmet_pickup_lv2\\",\\"misc\\":[{\\"count\\":48,\\"item_id\\":\\"shotgun\\"},{\\"count\\":120,\\"item_id\\":\\"bullet\\"},{\\"count\\":2,\\"item_id\\":\\"health_pickup_combo_small\\"},{\\"count\\":2,\\"item_id\\":\\"health_pickup_health_small\\"}],\\"weapon_1\\":\\"mp_weapon_semipistol\\",\\"weapon_2\\":\\"mp_weapon_shotgun_pistol\\"}\",\"gdur\":\"0\"}"
更复杂
- 创建模型 class 不是一个选项,因为这个 json 是完全动态的
- 扁平化字典是不可能的,因为 json 可能有重复的键名,但在不同的层次结构下
- 我无法请求更改 Kafka 消息
我想做什么
最终用户将定义我需要检查是否找到匹配项的规则。例如,规则可以是 reward.xp == 200
或 reward.loot.rarity == high
或 status == complete
。这些规则将由用户定义,因此不能进行硬编码,但是我可以决定使用数据结构来保存它们。因此,对于每条 Kafka 消息,我都必须遍历该字典并尝试找到与规则匹配的内容。
我试过的
我试过 JsonConvert.Deserialize
到 object
、dynamic
、ExpandoObject
和 none 可以处理嵌套的 json 层次结构。他们刚刚得到第一级正确。 JObject.Parse
的结果也相同。
使用您喜欢的任何解析器解析 JSON(我使用 Newtonsoft.Json)。
然后递归访问层次结构,并使用每个 属性 值的完整路径作为键将每个 属性 复制到平面列表。然后您可以迭代该平面列表。
编辑:评论请求支持数组,所以这个版本支持。
https://dotnetfiddle.net/6ykHT0
using System;
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
string json = @"{
'reward': {
'xp': '200',
'gp': '150',
'loot': {
'item': 'sword',
'rarity': 'low',
'blah': {
'socks': 5
}
},
'arrayofint': [1,2,3,4],
'arrayofobj': [
{
'foo': 'bar',
'stuff': ['omg!', 'what?!']
},
{
'foo': 'baz',
'stuff': ['a', 'b']
}
],
'arrayofarray': [
[1,2,3],
[4,5,6]
],
'arrayofheterogenousjunk': [
'a',
2,
{ 'objprop': 1 },
['staahp!']
]
},
'achievement': 'win_match'
}";
JObject data = JObject.Parse(json);
IList<string> nodes = flattenJSON(data);
Console.WriteLine(string.Join(Environment.NewLine, nodes));
}
private static IList<string> flattenJSON(JToken token)
{
return _flattenJSON(token, new List<string>());
}
private static IList<string> _flattenJSON(JToken token, List<string> path)
{
var output = new List<string>();
if (token.Type == JTokenType.Object)
{
// Output the object's child properties
output.AddRange(token.Children().SelectMany(x => _flattenJSON(x, path)));
}
else if (token.Type == JTokenType.Array)
{
// Output each array element
var arrayIndex = 0;
foreach (var child in token.Children())
{
// Append the array index to the end of the last path segment - e.g. someProperty[n]
var newPath = new List<string>(path);
newPath[newPath.Count - 1] += "[" + arrayIndex++ + "]";
output.AddRange(_flattenJSON(child, newPath));
}
}
else if (token.Type == JTokenType.Property)
{
var prop = token as JProperty;
// Insert the property name into the path
output.AddRange(_flattenJSON(prop.Value, new List<string>(path) { prop.Name }));
}
else
{
// Join the path segments delimited with periods, followed by the literal value
output.Add(string.Join(".", path) + " = " + token.ToString());
}
return output;
}
}
输出:
reward.xp = 200
reward.gp = 150
reward.loot.item = sword
reward.loot.rarity = low
reward.loot.blah.socks = 5
reward.arrayofint[0] = 1
reward.arrayofint[1] = 2
reward.arrayofint[2] = 3
reward.arrayofint[3] = 4
reward.arrayofobj[0].foo = bar
reward.arrayofobj[0].stuff[0] = omg!
reward.arrayofobj[0].stuff[1] = what?!
reward.arrayofobj[1].foo = baz
reward.arrayofobj[1].stuff[0] = a
reward.arrayofobj[1].stuff[1] = b
reward.arrayofarray[0][0] = 1
reward.arrayofarray[0][1] = 2
reward.arrayofarray[0][2] = 3
reward.arrayofarray[1][0] = 4
reward.arrayofarray[1][1] = 5
reward.arrayofarray[1][2] = 6
reward.arrayofheterogenousjunk[0] = a
reward.arrayofheterogenousjunk[1] = 2
reward.arrayofheterogenousjunk[2].objprop = 1
reward.arrayofheterogenousjunk[3][0] = staahp!
achievement = win_match
旧版本(不支持阵列)
这不能正确支持数组 - 它会输出作为原始数组的 属性 的内容 JSON - 即它不会遍历数组。
https://dotnetfiddle.net/yZbwul
public static void Main()
{
string json = @"{
'reward': {
'xp': '200',
'gp': '150',
'loot': {
'item': 'sword',
'rarity': 'low',
'blah': {
'socks': 5
}
}
},
'achievement': 'win_match'
}";
JObject data = JObject.Parse(json);
IList<string> nodes = flattenJSON(data, new List<string>());
Console.WriteLine(string.Join(Environment.NewLine, nodes));
}
private static IList<string> flattenJSON(JObject obj, IList<string> path)
{
var output = new List<string>();
foreach (var prop in obj.Properties())
{
if (prop.Value.Type == JTokenType.Object)
{
output.AddRange(flattenJSON(prop.Value as JObject, new List<string>(path){prop.Name}));
}
else
{
var s = string.Join(".", new List<string>(path) { prop.Name }) + " = " + prop.Value.ToString();
output.Add(s);
}
}
return output;
}
输出:
reward.xp = 200
reward.gp = 150
reward.loot.item = sword
reward.loot.rarity = low
reward.loot.blah.socks = 5
achievement = win_match
我的应用程序收到包含 Dictionary<string,string>
作为属性之一的 Kafka 消息,它的值可能是一个嵌套的(但是动态的)json 字符串,我需要遍历这个未知的json。我正在努力寻找一种逻辑,甚至是最好的数据结构来进行这次迭代。
字典示例(模拟数据):
//could have complex nested json string as value
"reward":"{
'xp':'200',
'gp':'150',
'loot':'{
'item':'sword',
'rarity': 'low'
}'
}",
"achievement":"win_match"
// while other messages might be simple
"type":"generic_xp",
"percent":"100",
"status":"complete"
真实消息的序列化版本:
"{\"player_stats\":\"{\\"assist\\":0,\\"deaths\\":0,\\"kills\\":0,\\"team_index\\":2}\",\"round_attr\":\"{\\"max_player_count\\":4,\\"rdur\\":0,\\"round\\":1,\\"team_player_count\\":{\\"team_1\\":1,\\"team_2\\":0},\\"team_score\\":0}\",\"custom\":\"{\\"armor\\":\\"armor_pickup_lv2\\",\\"balance\\":550,\\"helmet\\":\\"helmet_pickup_lv2\\",\\"misc\\":[{\\"count\\":48,\\"item_id\\":\\"shotgun\\"},{\\"count\\":120,\\"item_id\\":\\"bullet\\"},{\\"count\\":2,\\"item_id\\":\\"health_pickup_combo_small\\"},{\\"count\\":2,\\"item_id\\":\\"health_pickup_health_small\\"}],\\"weapon_1\\":\\"mp_weapon_semipistol\\",\\"weapon_2\\":\\"mp_weapon_shotgun_pistol\\"}\",\"gdur\":\"0\"}"
更复杂
- 创建模型 class 不是一个选项,因为这个 json 是完全动态的
- 扁平化字典是不可能的,因为 json 可能有重复的键名,但在不同的层次结构下
- 我无法请求更改 Kafka 消息
我想做什么
最终用户将定义我需要检查是否找到匹配项的规则。例如,规则可以是 reward.xp == 200
或 reward.loot.rarity == high
或 status == complete
。这些规则将由用户定义,因此不能进行硬编码,但是我可以决定使用数据结构来保存它们。因此,对于每条 Kafka 消息,我都必须遍历该字典并尝试找到与规则匹配的内容。
我试过的
我试过 JsonConvert.Deserialize
到 object
、dynamic
、ExpandoObject
和 none 可以处理嵌套的 json 层次结构。他们刚刚得到第一级正确。 JObject.Parse
的结果也相同。
使用您喜欢的任何解析器解析 JSON(我使用 Newtonsoft.Json)。
然后递归访问层次结构,并使用每个 属性 值的完整路径作为键将每个 属性 复制到平面列表。然后您可以迭代该平面列表。
编辑:评论请求支持数组,所以这个版本支持。
https://dotnetfiddle.net/6ykHT0
using System;
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
string json = @"{
'reward': {
'xp': '200',
'gp': '150',
'loot': {
'item': 'sword',
'rarity': 'low',
'blah': {
'socks': 5
}
},
'arrayofint': [1,2,3,4],
'arrayofobj': [
{
'foo': 'bar',
'stuff': ['omg!', 'what?!']
},
{
'foo': 'baz',
'stuff': ['a', 'b']
}
],
'arrayofarray': [
[1,2,3],
[4,5,6]
],
'arrayofheterogenousjunk': [
'a',
2,
{ 'objprop': 1 },
['staahp!']
]
},
'achievement': 'win_match'
}";
JObject data = JObject.Parse(json);
IList<string> nodes = flattenJSON(data);
Console.WriteLine(string.Join(Environment.NewLine, nodes));
}
private static IList<string> flattenJSON(JToken token)
{
return _flattenJSON(token, new List<string>());
}
private static IList<string> _flattenJSON(JToken token, List<string> path)
{
var output = new List<string>();
if (token.Type == JTokenType.Object)
{
// Output the object's child properties
output.AddRange(token.Children().SelectMany(x => _flattenJSON(x, path)));
}
else if (token.Type == JTokenType.Array)
{
// Output each array element
var arrayIndex = 0;
foreach (var child in token.Children())
{
// Append the array index to the end of the last path segment - e.g. someProperty[n]
var newPath = new List<string>(path);
newPath[newPath.Count - 1] += "[" + arrayIndex++ + "]";
output.AddRange(_flattenJSON(child, newPath));
}
}
else if (token.Type == JTokenType.Property)
{
var prop = token as JProperty;
// Insert the property name into the path
output.AddRange(_flattenJSON(prop.Value, new List<string>(path) { prop.Name }));
}
else
{
// Join the path segments delimited with periods, followed by the literal value
output.Add(string.Join(".", path) + " = " + token.ToString());
}
return output;
}
}
输出:
reward.xp = 200
reward.gp = 150
reward.loot.item = sword
reward.loot.rarity = low
reward.loot.blah.socks = 5
reward.arrayofint[0] = 1
reward.arrayofint[1] = 2
reward.arrayofint[2] = 3
reward.arrayofint[3] = 4
reward.arrayofobj[0].foo = bar
reward.arrayofobj[0].stuff[0] = omg!
reward.arrayofobj[0].stuff[1] = what?!
reward.arrayofobj[1].foo = baz
reward.arrayofobj[1].stuff[0] = a
reward.arrayofobj[1].stuff[1] = b
reward.arrayofarray[0][0] = 1
reward.arrayofarray[0][1] = 2
reward.arrayofarray[0][2] = 3
reward.arrayofarray[1][0] = 4
reward.arrayofarray[1][1] = 5
reward.arrayofarray[1][2] = 6
reward.arrayofheterogenousjunk[0] = a
reward.arrayofheterogenousjunk[1] = 2
reward.arrayofheterogenousjunk[2].objprop = 1
reward.arrayofheterogenousjunk[3][0] = staahp!
achievement = win_match
旧版本(不支持阵列)
这不能正确支持数组 - 它会输出作为原始数组的 属性 的内容 JSON - 即它不会遍历数组。
https://dotnetfiddle.net/yZbwul
public static void Main()
{
string json = @"{
'reward': {
'xp': '200',
'gp': '150',
'loot': {
'item': 'sword',
'rarity': 'low',
'blah': {
'socks': 5
}
}
},
'achievement': 'win_match'
}";
JObject data = JObject.Parse(json);
IList<string> nodes = flattenJSON(data, new List<string>());
Console.WriteLine(string.Join(Environment.NewLine, nodes));
}
private static IList<string> flattenJSON(JObject obj, IList<string> path)
{
var output = new List<string>();
foreach (var prop in obj.Properties())
{
if (prop.Value.Type == JTokenType.Object)
{
output.AddRange(flattenJSON(prop.Value as JObject, new List<string>(path){prop.Name}));
}
else
{
var s = string.Join(".", new List<string>(path) { prop.Name }) + " = " + prop.Value.ToString();
output.Add(s);
}
}
return output;
}
输出:
reward.xp = 200
reward.gp = 150
reward.loot.item = sword
reward.loot.rarity = low
reward.loot.blah.socks = 5
achievement = win_match