【问题标题】:Deserialize JSON array of arrays into List of Tuples using Newtonsoft使用 Newtonsoft 将 JSON 数组反序列化为元组列表
【发布时间】:2018-01-11 07:53:31
【问题描述】:

我从在线服务提供商处收到如下所示的数据:

{
  name: "test data",
  data: [
    [ "2017-05-31", 2388.33 ],
    [ "2017-04-30", 2358.84 ],
    [ "2017-03-31", 2366.82 ],
    [ "2017-02-28", 2329.91 ]
  ],
}       

我想将其解析成如下所示的对象:

public class TestData
{
   public string Name;
   public List<Tuple<DateTime, double>> Data;
}

我唯一能找到的就是如何将一个对象数组解析成一个元组列表,例如:Json.NET deserialization of Tuple<...> inside another type doesn't work?

有没有办法编写一个自定义转换器来处理这个问题?

【问题讨论】:

  • 为什么是元组?在大多数情况下它们有点糟糕(C#7 除外,但这真的是另一回事)
  • 一个类也很好......但它是一种简单的方法来定义包含两种“不同”数据类型的东西......任何一种方式都可以。
  • @BrianRice:一般来说,可以编写一个小类来保存相同的数据并为其提供更有用的名称。 Item1 和 Item2 永远不会产生可读的代码......有时你需要元组的功能(例如它的相等逻辑)但通常一个小的命名类会完美地工作并且更具可读性。
  • 唯一的(如果有的话)优势是我只需要编写一个通用转换器......我可以将它用于任何可能类型的配对数据......你也会看到每当我使用元组时,我都会立即将这些项目作为命名变量提取出来......这有助于维护......但是,我确实听到了你在说什么。

标签: c# arrays json tuples deserialization


【解决方案1】:

如果有人对更通用的 ValueTuples 解决方案感兴趣

public class TupleConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var type = value.GetType();
        var array = new List<object>();
        FieldInfo fieldInfo;
        var i = 1;

        while ((fieldInfo = type.GetField($"Item{i++}")) != null)
            array.Add(fieldInfo.GetValue(value));

        serializer.Serialize(writer, array);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var argTypes = objectType.GetGenericArguments();
        var array = serializer.Deserialize<JArray>(reader);
        var items = array.Select((a, index) => a.ToObject(argTypes[index])).ToArray();

        var constructor = objectType.GetConstructor(argTypes);
        return constructor.Invoke(items);
    }

    public override bool CanConvert(Type type)
    {
        return type.Name.StartsWith("ValueTuple`");
    }
}

用法如下:

var settings = new JsonSerializerSettings();
settings.Converters.Add(new TupleConverter());

var list = new List<(DateTime, double)>
{
    (DateTime.Now, 7.5)
};
var json = JsonConvert.SerializeObject(list, settings);
var result = JsonConvert.DeserializeObject(json, list.GetType(), settings);

【讨论】:

    【解决方案2】:

    我会创建一个特定于任务的类,而不是使用元组。在这种情况下,您的 JSON 数据以字符串列表的形式出现,处理起来有点尴尬。一种方法是反序列化为List&lt;List&lt;string&gt;&gt;,然后再进行转换。例如,我会选择 3 个这样的课程:

    public class IntermediateTestData
    {
        public string Name;
        public List<List<string>> Data;
    }
    
    public class TestData
    {
        public string Name;
        public IEnumerable<TestDataItem> Data;
    }
    
    public class TestDataItem
    {
        public DateTime Date { get; set; }
        public double Value { get; set; }
    }
    

    现在像这样反序列化:

    var intermediate = JsonConvert.DeserializeObject<IntermediateTestData>(json);
    
    var testData = new TestData
    {
        Name = intermediate.Name,
        Data = intermediate.Data.Select(d => new TestDataItem
        {
            Date = DateTime.Parse(d[0]),
            Value = double.Parse(d[1])
        })
    
    };
    

    【讨论】:

    • 这是正确的方法。但是,我会使用 JArray 和 JObject 而不是 List 和 string。
    【解决方案3】:

    所以使用 JSON.NET LINQ,我设法让它按照您的规定工作......

    var result = JsonConvert.DeserializeObject<JObject>(json);
    var data = new TestData
    {
        Name = (string)result["name"],
        Data = result["data"]
            .Select(t => new Tuple<DateTime, double>(DateTime.Parse((string)t[0]), (double)t[1]))
            .ToList()
    };
    

    这是我写的完整测试

    public class TestData
    {
        public string Name;
        public List<Tuple<DateTime, double>> Data;
    }
    
    [TestMethod]
    public void TestMethod1()
    {
        var json =
        @"{
            name: ""test data"",
            data: [
            [ ""2017-05-31"", 2388.33 ],
            [ ""2017-04-30"", 2358.84 ],
            [ ""2017-03-31"", 2366.82 ],
            [ ""2017-02-28"", 2329.91 ]
            ],
        }";
    
        var result = JsonConvert.DeserializeObject<JObject>(json);
        var data = new TestData
        {
            Name = (string)result["name"],
            Data = result["data"]
                .Select(t => new Tuple<DateTime, double>(DateTime.Parse((string)t[0]), (double)t[1]))
                .ToList()
        };
    
        Assert.AreEqual(2388.33, data.Data[0].Item2);
    }
    

    但是,虽然这可能有效,但我同意其他 cmets/answers 的观​​点,即为此使用元组可能不是正确的方法。由于Tuple&lt;,&gt;Item1Item2 属性,从长远来看,使用具体的POCO 肯定会更容易维护。

    它们不是最具描述性的......

    【讨论】:

    • 我同意...重点是将数组数组转换为(某种)对象的列表...元组或类...您的技术无论哪种方式都可以。
    【解决方案4】:

    我从这里获取了通用的 TupleConverter:Json.NET deserialization of Tuple<...> inside another type doesn't work? 并制作了一个通用的 TupleListConverter。

    用法:

    public class TestData
    {
        public string Name;
        [Newtonsoft.Json.JsonConverter(typeof(TupleListConverter<DateTime, double>))]
        public List<Tuple<DateTime, double>> Data;
    }
    
    public void Test(string json)
    {
        var testData = JsonConvert.DeserializeObject<TestData>(json);
        foreach (var tuple in testData.data)
        {
            var dateTime = tuple.Item1;
            var price = tuple.Item2;
            ... do something...
        }
    }
    

    转换器:

    public class TupleListConverter<U, V> : Newtonsoft.Json.JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(Tuple<U, V>) == objectType;
        }
    
        public override object ReadJson(
            Newtonsoft.Json.JsonReader reader,
            Type objectType,
            object existingValue,
            Newtonsoft.Json.JsonSerializer serializer)
        {
            if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
                return null;
    
            var jArray = Newtonsoft.Json.Linq.JArray.Load(reader);
            var target = new List<Tuple<U, V>>();
    
            foreach (var childJArray in jArray.Children<Newtonsoft.Json.Linq.JArray>())
            {
                var tuple = new Tuple<U, V>(
                    childJArray[0].ToObject<U>(),
                    childJArray[1].ToObject<V>()
                );
                target.Add(tuple);
            }
    
            return target;
        }
    
        public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-13
      • 2017-10-16
      • 2013-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-10
      相关资源
      最近更新 更多