使用 C# 解析复杂的 JSON
Posted
技术标签:
【中文标题】使用 C# 解析复杂的 JSON【英文标题】:Parsing Complex JSON with C# 【发布时间】:2017-12-05 10:00:54 【问题描述】:我是 JSON 的新手,我有一些 JSON 试图用 C# 解析。
我尝试过创建一个由数据表示的类,但我的属性名称是基于时间的,因此我必须对我的数据合约进行硬编码。我尝试过 JSON.NET 和 LINQ 对数据进行排序,但由于奇怪的对象/属性而不断得到空值。
我对 JSON 还是很陌生,所以我确信有一个简单的解决方法,我只是不知道如何正确地问这个问题。谢谢您的帮助。
下面是我正在努力解析的一小部分 JSON。再次感谢。
"Meta Data":
"1. Information": "Intraday (1min) prices and volumes",
"2. Symbol": "MU",
"3. Last Refreshed": "2017-05-30 16:00:00",
"4. Interval": "1min",
"5. Output Size": "Full size",
"6. Time Zone": "US/Eastern"
,
"Time Series (1min)":
"2017-05-30 16:00:00":
"1. open": "30.7200",
"2. high": "30.7300",
"3. low": "30.7000",
"4. close": "30.7000",
"5. volume": "1390302"
,
"2017-05-30 15:59:00":
"1. open": "30.7750",
"2. high": "30.7800",
"3. low": "30.7200",
"4. close": "30.7250",
"5. volume": "380134"
请注意,"Time Series"
属性以 1 分钟、5 分钟、15 分钟、30 分钟、60 分钟为间隔,即"Time Series (##min)"
用于各种##min
。
【问题讨论】:
我能想到的第一件事是使用日期范围作为索引。例如,代替 "Time Series (1min)","Time Series": "1min" 然后取消对日期时间键的连字符,使其看起来像这样:20170530160000 感谢您的评论。问题是这是我从网站获取的 JSON,而不是创建的。就这样我理解你,你的说法是清理属性而不是解析。是否有一个库可以更改 JSON 格式本身?谢谢 @user1762172 理想情况下,JSON 中的键应该是您选择的编程语言中的有效属性名称。在这里,您不能基于您获得的 JSON 创建 C# 属性。相反,您可以查询 JObject(返回 JSON.Net 的默认反序列化类型),如 here 和 here 所示,或根据您的要求进一步搜索。 1) 是否还有其他可能的"Time Series"
属性,例如""Time Series (10min)"
?还是一组可能的时间序列是固定的? 2) 我尝试过 JSON.NET 和 LINQ 对数据进行排序,但由于奇怪的对象/属性而不断得到空值。你能分享你的尝试吗?
@dbc 是的,“时间序列”以 1 分钟、5 分钟、15 分钟、30 分钟、60 分钟为间隔,就像你说的“时间序列(##min)”。我也尝试过 JSON.NET to LINQ 并得到一个 NULLException 错误。我可以通过硬编码其中的值来获得它,例如dynamic converted = JsonConvert.DeserializeObject您可以使用这些类来反序列化 那个特定的 Json 文件,这里我假设Time Series (1min)
中的两个对象在每个 json 文件中都具有相同的名称。但考虑到它们是日期,我很确定每次下载 json 时都会有所不同。
只是为了让您了解您可以使用Newtonsoft Json
属性做什么:
public class MetaData
[JsonProperty("1. Information")]
public string Information get; set;
[JsonProperty("2. Symbol")]
public string Symbol get; set;
[JsonProperty("3. Last Refreshed")]
public string LastRefreshed get; set;
[JsonProperty("4. Interval")]
public string Interval get; set;
[JsonProperty("5. Output Size")]
public string OutputSize get; set;
[JsonProperty("6. Time Zone")]
public string TimeZone get; set;
public class T1
[JsonProperty("1. Information")]
public string Open get; set;
[JsonProperty("2. high")]
public string High get; set;
[JsonProperty("3. low")]
public string Low get; set;
[JsonProperty("4. close")]
public string Close get; set;
[JsonProperty("5. volume")]
public string Volume get; set;
public class T2
[JsonProperty("1. Information")]
public string Open get; set;
[JsonProperty("2. high")]
public string High get; set;
[JsonProperty("3. low")]
public string Low get; set;
[JsonProperty("4. close")]
public string Close get; set;
[JsonProperty("5. volume")]
public string Volume get; set;
public class TimeSeries
[JsonProperty("2017-05-30 16:00:00")]
public T1 T1 get; set;
[JsonProperty("2017-05-30 15:59:00")]
public T2 T2 get; set;
public class RootObject
[JsonProperty("Meta Data")]
public MetaData MetaData get; set;
[JsonProperty("Time Series (1min)")]
public TimeSeries TimeSeries get; set;
然后,当你反序列化时:
var deserializedObject = JsonConvert.DeserializeObject<RootObject>(
File.ReadAllText("exampleFile.json"));
如果您能告诉我们更多关于您的 json 文件的信息,我们可以为您提供更好的帮助。
【讨论】:
哇,谢谢!这堂课比我做的那堂课要干净得多。就像你说的时间改变,但不是每次你打电话。你可以动态设置 JsonProperty 吗?或使用反射? JSON 文件是股票数据,因此当您调用它时,时间保持不变,但每天都有新信息。再次感谢您 您不能在运行时更改属性。它们嵌入到程序集的元数据中。我可以通过反射访问特定的 JsonProperty,但我只会更改特定实例的内部状态;当我再次加载属性时,我会得到一个原始值。【参考方案2】:您希望将您的 JSON 系列反序列化为某种 c# 类型,但是如何做到这一点并不明显,因为 JSON 对象具有固定和可变属性名称,它们都不对应于有效的 c# 标识符。具体来说:
您的根对象有一个属性"Meta Data"
,它对应于具有字符串键/值对集合的 JSON 对象。按照this question 的回答,您可以将其绑定到字典属性:
[JsonProperty("Meta Data")]
public Dictionary<string, string> MetaData get; set;
此外,您的根对象具有任意属性集,其名称类似于“时间序列 (##min)”,用于各种 ##min
,具有对应于 Dictionary<DateTime, Dictionary<string, decimal>>
的固定架构 .因为这些属性有一个固定的模式,所以你不能像Deserialize json with known and unknown fields 中建议的那样只使用[JsonExtensionData]
。相反,您可以使用来自How to deserialize a child object with dynamic (numeric) key names? 的转换器TypedExtensionDataConverter<TObject>
来反序列化您的根对象,使时间序列属性如下:
[JsonTypedExtensionData]
public Dictionary<string, Dictionary<DateTime, Dictionary<string, decimal>>> TimeSeries get; set;
因此您可以按如下方式设计您的根对象:
[JsonConverter(typeof(TypedExtensionDataConverter<RootObject>))]
public class RootObject
public RootObject()
// Ensure dictionaries are allocated.
this.MetaData = new Dictionary<string, string>();
this.TimeSeries = new Dictionary<string, Dictionary<DateTime, Dictionary<string, decimal>>>();
[JsonProperty("Meta Data")]
public Dictionary<string, string> MetaData get; set;
[JsonTypedExtensionData]
public Dictionary<string, Dictionary<DateTime, Dictionary<string, decimal>>> TimeSeries get; set;
TypedExtensionDataConverter<RootObject>
从this answer 逐字复制。
示例fiddle。
请注意,如果每个时间序列时间的属性名称集"1. open"
、"2. high"
等是固定的,则可以使用类似于@FrancescoB 的answer 中的T1
的预定义类型来代替Dictionary<string, decimal>
:
[JsonConverter(typeof(TypedExtensionDataConverter<RootObject>))]
public class RootObject
public RootObject()
// Ensure dictionaries are allocated.
this.MetaData = new Dictionary<string, string>();
this.TimeSeries = new Dictionary<string, Dictionary<DateTime, TimeSeriesData>>();
[JsonProperty("Meta Data")]
public Dictionary<string, string> MetaData get; set;
[JsonTypedExtensionData]
public Dictionary<string, Dictionary<DateTime, TimeSeriesData>> TimeSeries get; set;
public class TimeSeriesData
[JsonProperty("1. open")]
public decimal Open get; set;
[JsonProperty("2. high")]
public decimal High get; set;
[JsonProperty("3. low")]
public decimal Low get; set;
[JsonProperty("4. close")]
public decimal Close get; set;
[JsonProperty("5. volume")]
public decimal Volume get; set;
示例fiddle #2。
【讨论】:
我想为您考虑这一点,因为它看起来非常详细和复杂。我遇到的问题是我试图将 JSON 反序列化为一个表,而使用你的代码,无论我做什么,我都无法做到这一点。例如,TimeSeriesData 属性将是列,而行将是每个时间间隔。我已经研究过使用数据表并写入外部 Excel,但理想情况下希望在将其推送到外部文件之前进行解析。任何可以为我指明正确方向的信息将不胜感激,再次感谢。 我发现寻找一个好的欺骗目标的最佳答案之一。可悲的是它不够高。通过漂亮的编译清除。【参考方案3】:您可以尝试使用 JsonCovert,如下所示
string json = @"
'Meta Data':
'1. Information': 'Intraday (1min) prices and volumes',
'2. Symbol': 'MU',
'3. Last Refreshed': '2017-05-30 16:00:00',
'4. Interval': '1min',
'5. Output Size': 'Full size',
'6. Time Zone': 'US/Eastern'
,
'Time Series (1min)':
'2017-05-30 16:00:00':
'1. open': '30.7200',
'2. high': '30.7300',
'3. low': '30.7000',
'4. close': '30.7000',
'5. volume': '1390302'
,
'2017-05-30 15:59:00':
'1. open': '30.7750',
'2. high': '30.7800',
'3. low': '30.7200',
'4. close': '30.7250',
'5. volume': '380134'
";
var jsonConvertedData = JsonConvert.DeserializeObject(json);
这会将 json 字符串解析为 json 对象。
【讨论】:
谢谢。这行得通,但现在的困难是通过 JSON 对象进行排序(LINQ/foreach) 您可以参考***.com/questions/14417235/…【参考方案4】:要实现TimeSeries
中的两个属性动态命名的效果,可以手动解析json树(使用Newtonsoft Json
库):
反序列化域:
public class MetaData
public string Information get; set;
public string Symbol get; set;
public DateTime LastRefreshed get; set;
public string Interval get; set;
public string OutputSize get; set;
public string TimeZone get; set;
public class TimeSeriesInfos
public double Open get; set;
public double High get; set;
public double Low get; set;
public double Close get; set;
public double Volume get; set;
public class TimeSeries
public TimeSeriesInfos T1 get; set;
public TimeSeriesInfos T2 get; set;
public class RootObject
public MetaData MetaData get; set;
public TimeSeries TimeSeries get; set;
然后像这样反序列化它:
var jsonObjectTree = JsonConvert.DeserializeObject<JObject>(
File.ReadAllText("exampleFile.json"));
const string metaDataName = "Meta Data";
const string timeSeriesName = "Time Series (1min)";
const string openName = "1. open";
const string highName = "2. high";
const string lowName = "3. low";
const string closeName = "4. close";
const string volumeName = "5. volume";
// You can obtain dynamically those two properties
string t1Name = "2017-05-30 16:00:00";
string t2Name = "2017-05-30 15:59:00";
var deserializedObject = new RootObject()
MetaData = new MetaData()
Information = jsonObjectTree[metaDataName]["1. Information"].Value<string>(),
Symbol = jsonObjectTree[metaDataName]["2. Symbol"].Value<string>(),
LastRefreshed = DateTime.ParseExact(jsonObjectTree[metaDataName]["3. Last Refreshed"].Value<string>(), "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
Interval = jsonObjectTree[metaDataName]["4. Interval"].Value<string>(),
OutputSize = jsonObjectTree[metaDataName]["5. Output Size"].Value<string>(),
TimeZone = jsonObjectTree[metaDataName]["6. Time Zone"].Value<string>()
,
TimeSeries = new TimeSeries()
T1 = new TimeSeriesInfos()
Open = jsonObjectTree[timeSeriesName][t1Name][openName].Value<double>(),
High = jsonObjectTree[timeSeriesName][t1Name][highName].Value<double>(),
Low = jsonObjectTree[timeSeriesName][t1Name][lowName].Value<double>(),
Close = jsonObjectTree[timeSeriesName][t1Name][closeName].Value<double>(),
Volume = jsonObjectTree[timeSeriesName][t1Name][volumeName].Value<double>()
,
T2 = new TimeSeriesInfos()
Open = jsonObjectTree[timeSeriesName][t2Name][openName].Value<double>(),
High = jsonObjectTree[timeSeriesName][t2Name][highName].Value<double>(),
Low = jsonObjectTree[timeSeriesName][t2Name][lowName].Value<double>(),
Close = jsonObjectTree[timeSeriesName][t2Name][closeName].Value<double>(),
Volume = jsonObjectTree[timeSeriesName][t2Name][volumeName].Value<double>()
;
【讨论】:
感谢这段代码很好用!我从中学到了很多。我唯一不完全理解的是如何动态获取字符串类型的属性?字符串 t1Name = "2017-05-30 16:00:00";部分。谢谢。 我不知道你的应用程序的逻辑,但如果你知道将在你的 json 中的DateTime
,你可以这样做:myDateTime.ToString("yyyy-MM-dd hh:mm:ss")
。问题是现在 1Name
和 t2Name
可以分配给您想要正确反序列化 json 的值。以上是关于使用 C# 解析复杂的 JSON的主要内容,如果未能解决你的问题,请参考以下文章