Deserialize JSON array of arrays into List of Tuples using Newtonsoft

牧云@^-^@ 提交于 2019-12-05 23:51:58

问题


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

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

I would like to parse it into an object that looks like this:

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

The only thing I have been able to find is how to parse an array of objects into a list of tulples, for example: Json.NET deserialization of Tuple<...> inside another type doesn't work?

Is there a way to write a custom converter that would handle this?


回答1:


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



回答2:


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

};



回答3:


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




回答4:


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


来源:https://stackoverflow.com/questions/45488785/deserialize-json-array-of-arrays-into-list-of-tuples-using-newtonsoft

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!