I have a class that looked like this:
public class MyData : IList<Tuple<double,double>>
The idea being that you have a list of pairs of values. Simple enough. But I wanted this to be serialized such that it looks like an array of an array of doubles (i.e. double[][]
) rather than a list of tuples. It should look like this when serialized:
[[1,1],[2,2],[3,3]]
So I created a simple JsonConverter
that will do it. It has a very simple WriteJson
method that looks like this:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var obj = value as MyData;
double[][] dataArray = (from dp in obj._data
select new[] { dp.Item1, dp.Item2 }).ToArray();
var ser = new JsonSerializer();
ser.Serialize(writer, dataArray);
}
And I decorate MyData
with the appropriate attribute:
[JsonConverter(typeof(MyDataJsonConverter))]
public class MyData: IList<Tuple<double,double>>
And everything works fine. Now however, I want to extend this and make MyData
generic:
public class MyData<S,T>: IList<Tuple<S,T>>
And this is were I run into problems. Initially, I tried making MyDataJsonConverter
also generic:
[JsonConverter(typeof(MyDataJsonConverter<S,T>))]
public class MyData<S,T>: IList<Tuple<S,T>>
But that isn't allowed because you can't use generics with attributes.
So I have to keep MyDataJsonConverter
non-generic, but I need to figure out how to flatten my collection of tuples into an array (presumably of object[][]
now) such that when I serialize my data would look like:
[[1,2],[2,3]]
or:
[["foo":1],["bar":2]]
or even:
[["foo":"bar"],["what":"ever"]]
Any ideas on how to handle it? In WriteJson
, I can no longer cast value
to MyData
because I won't know the type parameters, so everything just pretty much falls apart at that point.
I could just create separate classes for each type combination (so one for Tuple<double,double>
and one for Tuple<string,double>
and so on...), but I was wondering if there was a better way before I go brute force on it.
Okay, so here's basically what I ended up doing (in case anybody else needs something similar). My collection looks something like this:
[JsonConverter(typeof(MyDataJsonConverter))]
public class MyData<S,T> : IList<Tuple<S,T>>
{
private List<Tuple<S, T>> _data;
// all the implementation of IList...
}
And my custom converter (now no longer nested inside MyData
) looks like this:
internal class MyDataJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(MyData<,>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Note: this is strictly for serializing, deserializing is a whole other
// ball of wax that I don't currently need!
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var obj = value as IEnumerable<dynamic>;
object[][] dataArray = (from dp in obj
select new object[] { dp.Item1, dp.Item2 }).ToArray();
var ser = new JsonSerializer();
ser.Serialize(writer, dataArray);
}
public override bool CanRead
{
get
{
return false;
}
}
}
The first thing is to use in obj
, not to couple yourself to the private members of the class, but to the interface it exposes. Then the only problem you have is to serialize Tuple<S, T>
held through an object
reference with unknown S
and T
. You can do it as follows, for example:
public static Tuple<object, object> UnpackUnknownTuple(object tuple)
{
var item1Property = tuple.GetType().GetProperty("Item1");
var item2Property = tuple.GetType().GetProperty("Item2");
return Tuple.Create(
item1Property.GetValue(tuple, null),
item2Property.GetValue(tuple, null));
}
public override void WriteJson(
JsonWriter writer,
object value,
JsonSerializer serializer)
{
var obj = value as IEnumerable;
var tuples = obj.Cast<object>().Select(UnpackUnknownTuple);
object[][] dataArray = (from dp in tuples
select new[] { dp.Item1, dp.Item2 }).ToArray();
var ser = new JsonSerializer();
ser.Serialize(writer, dataArray);
}
Another possibility is to convert to string
instead of object
but I guess it can interfere with some serialization settings. item1Property
and item2Property
can possibly be cached using first element of the list if there should be any performance problems.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With