Deserialize JSON array of arrays into List of Tuples using Newtonsoft

前端 未结 4 702
半阙折子戏
半阙折子戏 2021-01-05 09:48

I am receiving data that looks like this from an online service provider:

{
  name: \"test data\",
  data: [
    [ \"2017-05-31\", 2388.33 ],
    [ \"2017-04         


        
相关标签:
4条回答
  • 2021-01-05 10:34

    Rather than use tuples, I would create a class that is specific to the task. In this case your JSON data comes in as a list of lists of strings which is a bit awkward to deal with. One method would be to deserialise as List<List<string>> and then convert afterwards. For example, I would go with 3 classes like this:

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

    Now deserialise like this:

    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])
        })
    
    };
    
    0 讨论(0)
  • 2021-01-05 10:38

    I took the generic TupleConverter from here: Json.NET deserialization of Tuple<...> inside another type doesn't work? And made a generic TupleListConverter.

    Usage:

    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...
        }
    }
    

    Converter:

    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);
        }
    }
    
    0 讨论(0)
  • 2021-01-05 10:41

    So using JSON.NET LINQ, I managed to get it to work as you prescribed...

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

    This is the full test I wrote

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

    However, while this may work, I am in agreement with the rest of the comments/answers that using tuples for this is probably not the correct way to go. Using concrete POCO's is definitely going to be a hell of a lot more maintainable in the long run simply because of the Item1 and Item2 properties of the Tuple<,>.

    They are not the most descriptive...

    0 讨论(0)
  • 2021-01-05 10:43

    If anyone is interested in a more generic solution for 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`");
        }
    }
    

    Usage is as follows:

    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);
    
    0 讨论(0)
提交回复
热议问题