I am receiving data that looks like this from an online service provider:
{
name: \"test data\",
data: [
[ \"2017-05-31\", 2388.33 ],
[ \"2017-04
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])
})
};
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);
}
}
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...
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);