使用 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(JSON2); 【参考方案1】:

您可以使用这些类来反序列化 那个特定的 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&lt;DateTime, Dictionary&lt;string, decimal&gt;&gt;固定架构 .因为这些属性有一个固定的模式,所以你不能像Deserialize json with known and unknown fields 中建议的那样只使用[JsonExtensionData]。相反,您可以使用来自How to deserialize a child object with dynamic (numeric) key names? 的转换器TypedExtensionDataConverter&lt;TObject&gt; 来反序列化您的根对象,使时间序列属性如下:

[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&lt;RootObject&gt; 从this answer 逐字复制。

示例fiddle。

请注意,如果每个时间序列时间的属性名称集"1. open""2. high" 等是固定的,则可以使用类似于@FrancescoB 的answer 中的T1 的预定义类型来代替Dictionary&lt;string, decimal&gt;:

[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")。问题是现在 1Namet2Name 可以分配给您想要正确反序列化 json 的值。

以上是关于使用 C# 解析复杂的 JSON的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中解析复杂的 JSON?最终我想在 DataGridView 中显示结果 [关闭]

C# 解析复杂的Json文件

c#中怎么解析多层json数据

使用 C# 解析 JSON 文本文件

C#使用Json

使用Gson解析复杂的json数据