【问题标题】:Parsing Complex JSON with C#使用 C# 解析复杂的 JSON
【发布时间】: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 的默认反序列化类型),如 herehere 所示,或根据您的要求进一步搜索。
  • 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);

标签: c# json linq api json.net


【解决方案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 对象。

【讨论】:

【解决方案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 的值。
猜你喜欢
  • 2013-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多