【问题标题】:Deserializing Json array with variable names first using C# Json.NET首先使用 C# Json.NET 反序列化具有变量名的 Json 数组
【发布时间】:2016-03-21 14:07:11
【问题描述】:

我从人口普查局的公共 api 获得了一个不规则的 JSON 数组。 变量名都在第一个元素中,我无法反序列化它。

http://api.census.gov/data/2014/pep/agesex?get=AGE,POP,SEX&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 行,位置 2.

【问题讨论】:

  • 我很确定您必须将其反序列化为数组数组,然后将其转换为您想要的格式。
  • 这是一个例子:dotnetfiddle.net/Cr0aRL
  • @AndyJ 你的小提琴看起来很不错。愿意在此处将其转换为答案吗?
  • 哇@AndyJ。我忘记了dotnetfiddle。这是一个非常好的解决方案。我同意derpirscher,您应该将其转换为答案。
  • @derpirscher 我最初写了那个小提琴作为答案,但是当我回来时已经有两个答案的代码大致相同,所以我只留下了评论。但是,由于对此有需求,我已经编写了完整的答案,其中包含更多细节和对未来自动化的建议。谢谢你的好话:)

标签: c# json serialization json.net


【解决方案1】:

这有两个部分。

首先将 JSON 转换为 C# 中可用的数据,然后将这些数据转换为漂亮的对象。

这是以下代码的工作 dotNetFiddle.net 示例:https://dotnetfiddle.net/Cr0aRL

JSON 中的每一行都由一个字符串数组组成。 所以这是一个字符串数组的数组。 在 C# 中可以写成 string[][]。

因此,您可以使用 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);

上面的一切都是非常手动的。

您必须知道您期望的标题,然后手动查找索引,然后手动将行映射到对象。

对于一个简单的用例来说,这样做是很有可能的。但在更大和/或更复杂的系统中,您可能希望/需要自动化这些步骤。

自动化这些步骤是可能的,但超出了此答案的范围,因为您如何处理它可能取决于许多不同的因素。

【讨论】:

    【解决方案2】:

    您可以自定义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.Net 序列化“正常工作”,那么这是一个不错的选择。
    【解决方案3】:

    您必须自己进行处理,因为 json 反序列化器无法知道如何将值放入相应的变量中。

    如果你知道,这就是这个结构,你可以例如添加一个适当的构造函数

    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));
    

    如果您的结构不同,您将不得不编写一些更复杂的映射代码。

    【讨论】:

    • 谢谢。我以前很怕那个。我希望有一个我不知道的反序列化器设置。我认为您的解决方案是最简单的解决方案,并为我提供了一些验证机会。
    【解决方案4】:

    您必须进行一些自定义映射,因为您的 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();
    

    【讨论】:

    • 您应该跳过数组的第一个元素,因为它包含带有变量名称的标题。
    【解决方案5】:
    //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; }       
    
    }
    }
    

    【讨论】:

    • 请解释一下答案
    猜你喜欢
    • 2015-03-18
    • 1970-01-01
    • 1970-01-01
    • 2015-08-03
    • 1970-01-01
    • 2013-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多