我从人口普查局的公共API中获得了一个不规则的JSON数组。变量名称都在第一个元素中,我在反序列化它时遇到了问题。
http://api.census.gov/data/2014/pep/agesex?get=AGE,POP,&for=us:*&DATE=7
给我这样的JSON:
[["AGE","POP","SEX","DATE","us"],
["0","3948350","0","7","1"],
["1","3962123","0","7","1"],
["2","3957772","0","7","1"],
["3","4005190","0","7","1"],
["4","4003448","0","7","1"],
["5","4004858","0","7","1"],
["6","4134352","0","7","1"],
["7","4154000","0","7","1"]]
我可以使用以下方法成功反序列化它:
var test1 = JsonConvert.DeserializeObject<String[][]>(jsonStr);
但是,我正在尝试将其反序列化为这样的类:
public class TestClass
{
public string AGE { get; set; }
public string POP { get; set; }
public string SEX { get; set; }
public string DATE { get; set; }
public string us { get; set; }
}
我正在尝试这样做:
var test2 = JsonConvert.DeserializeObject<TestClass[]>(jsonStr);
但是我遇到了以下异常:
类型为"Newtonsoft.Json.JsonSerializationException"的异常 发生在 Newtonsoft.Json 中.dll但未在用户代码中处理
其他信息:无法创建和填充列表类型 测试类。路径"[0]",第 1 行,位置 阿拉伯数字。
部分。
首先是将 JSON 转换为 C# 中可用的数据,其次是将数据转换为漂亮的对象。
下面是以下代码的工作 dotNetFiddle.net 示例:https://dotnetfiddle.net/Cr0aRL
JSON 中的每一行都由一个字符串数组组成。所以这是一个字符串数组的数组。在 C# 中,可以写成字符串 [][]。
因此,要使用 JSON.Net 将 JSON 转换为可用数据,您可以执行以下操作:
var json = "[["AGE","POP","SEX","DATE","us"],["0","3948350","0","7","1"],["1","3962123","0","7","1"],["2","3957772","0","7","1"],["3","4005190","0","7","1"],["4","4003448","0","7","1"],["5","4004858","0","7","1"],["6","4134352","0","7","1"],["7","4154000","0","7","1"]]";
var rawData = JsonConvert.DeserializeObject<string[][]>(json);
接下来是将数据转换为对象。
第一行是标题,包含列名,所以我们要抓取它,然后找出每个列名的列索引。
var headerRow = rawData.First();
var ageIndex = Array.IndexOf(headerRow, "AGE");
var popIndex = Array.IndexOf(headerRow, "POP");
var sexIndex = Array.IndexOf(headerRow, "SEX");
var dateIndex = Array.IndexOf(headerRow, "DATE");
var usIndex = Array.IndexOf(headerRow, "us");
现在我们有了索引,我们需要获取每一行,并将其转换为适当的对象。为此,我使用了 LINQ,因为它非常擅长以清晰的方式表示数据处理。
var testData = rawData
.Skip(1) //The first row is a header, not data
.Select(dataRow => new TestClass()
{
AGE = dataRow[ageIndex],
POP = dataRow[popIndex],
SEX = dataRow[sexIndex],
DATE = dataRow[dateIndex],
us = dataRow[usIndex]
});
最后进行一些测试,以确保您拥有所需的数据。
//Get the second data row as an example
var example = testData.Skip(1).First();
//Output example POP to check value
Console.WriteLine(example.POP);
以上所有内容都是非常手动的。
您必须知道所需的标题,然后手动查找索引,然后手动将行映射到对象。
对于一个简单的用例来说,这样做很有可能很好。但在更大和/或更复杂的系统中,您可能希望/需要自动执行这些步骤。
自动化这些步骤是可能的,但超出了本答案的范围,因为您如何处理它可能取决于许多不同的因素。
您可以创建自定义 JsonConverter 来处理反序列化期间的此转换。 转换代码实际上与此处的其他答案没有太大区别,除了它被封装到一个单独的类中,这样您就不会将主代码与转换详细信息混淆。 从您的主代码的角度来看,它"只是工作"。
以下是编写转换器的方法:
public class TestClassArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(TestClass[]));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray table = JArray.Load(reader);
TestClass[] items = new TestClass[table.Count - 1];
for (int i = 1; i < table.Count; i++)
{
JArray row = (JArray)table[i];
items[i - 1] = new TestClass
{
AGE = (string)row[0],
POP = (string)row[1],
SEX = (string)row[2],
DATE = (string)row[3],
us = (string)row[4]
};
}
return items;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
以下是您将如何使用它:
var test2 = JsonConvert.DeserializeObject<TestClass[]>(jsonStr, new TestClassArrayConverter());
小提琴:https://dotnetfiddle.net/68Q0KT
您必须自己进行处理,因为 json 反序列化程序无法知道如何将值放入 respecitve 变量中。
如果你知道,这将是这个结构,例如你可以添加一个适当的构造函数
public TestClass(string[] values) {
AGE = values[0];
...
}
到你的班级。然后将结果序列化为字符串数组数组,然后将内部数组传递给构造函数。
var t1 = JsonConvert.DeserializeObject<string[][]>(jsonStr);
//skip the first entry, as this contains the headers
var t2 = t1.Skip(1).Select(x=> new TestClass(x));
如果结构不同,则必须编写一些更复杂的映射代码。
您必须执行一些自定义映射,因为您的 Json 没有任何命名约定,因此您必须处理数组和索引格式的数据。这将起作用:
var jsonStr = "[["AGE","POP","SEX","DATE","us"], ["0","3948350","0","7","1"], ["1","3962123","0","7","1"], ["2","3957772","0","7","1"], ["3","4005190","0","7","1"], ["4","4003448","0","7","1"], ["5","4004858","0","7","1"], ["6","4134352","0","7","1"], ["7","4154000","0","7","1"]]";
var test2 = JsonConvert.DeserializeObject<string[][]>(jsonStr);
var test3 = test2.Select(x => new TestClass()
{
AGE = x[0].ToString(),
POP = x[1].ToString(),
SEX = x[2].ToString(),
DATE = x[3].ToString(),
us = x[4].ToString()
}).ToList();
//test Case
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace ApiController.Test
{
[TestClass]
public class DownloadIrregularJsonStringObjects
{
string ApiKey => "YourPersonalCensusKey";
/// <summary>
/// You have to get your own ApiKey from the Census Website
/// </summary>
[TestMethod]
public void TestGetItem()
{
string url = $"http://api.census.gov/data/timeseries/healthins/sahie?get=NIC_PT,NAME,NUI_PT&for=county:*&in=state:*&time=2015&key={YourPersonalCensusKey}";
string expected = "Autauga County, AL";
IList<HealthData> actual = ApiController.DownloadIrregularJsonStringObjects.GetCensusHealthData(url);
Assert.AreEqual(actual[0].NAME, expected);
}
}
}
///Actual Assembly
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace ApiController
{
public class DownloadIrregularJsonStringObjects
{
public static IList<HealthData> GetCensusHealthData(string url)
{
var json = GetData(url);
var rawData = JsonConvert.DeserializeObject<string[][]>(json);
var headerRow = rawData.First();
var nic_pt_Index = Array.IndexOf(headerRow, "NIC_PT");
var name_Index = Array.IndexOf(headerRow, "NAME");
var nui_pt_Index = Array.IndexOf(headerRow, "NUI_PT");
IList<HealthData> retVal = new List<HealthData>();
foreach (var r in rawData.Skip(1))
{
HealthData dataRow = new HealthData();
dataRow.NIC_PT = r[nic_pt_Index];
dataRow.NAME = r[name_Index];
dataRow.NUI_PT = r[nui_pt_Index];
retVal.Add(dataRow);
}
return retVal;
}
private static string GetData(string url)
{
using (var w = new WebClient())
{
var jsonData = string.Empty;
jsonData = w.DownloadString(url);
return jsonData;
}
}
}
public class HealthData
{
public string NIC_PT { get; set; }
public string NAME { get; set; }
public string NUI_PT { get; set; }
}
}