Polymorphic lists in RavenDB / JSON
Posted by Tobi
Recently I'm doing a lot of playing around with RavenDB, a pure .Net Document database.
By default RavenDB can't handle documents containing lists of different types. That's not exactly RavenDB's fault, as the documents are stored as JSON. One way to solve this, is by providing a custom JsonContractResolver, as Ayende already pointed out.
While using JsonContractResolver definitly works, I think, I would prefer to do this using an attribute. Fortunately Newtonsoft.Json already allows this, so I can do:
public interface IBar
{
}
public class Foo
{
public string Id { get; set; }
[JsonConverter(typeof(PolymorphicListConverter))]
public List<IBar> Bars { get; set; }
[JsonConverter(typeof(PolymorphicListConverter<List<IBar>>))]
public IList<IBar> NonConcreteBars { get; set; }
[JsonConverter(typeof(PolymorphicListConverter))]
public ArrayList NonGenericBars { get; set; }
}
public class BarMaid: IBar
{
public string Something { get; set;}
}
public class BarTender: IBar
{
public string SomethingElse { get; set;}
}
Note that PolymorphicListConverter can take a generic type argument, because it might need to know, what type of list to create on deserialization, if the property doesn't use a concrete type.
The PolymorphicListConvert will now take care of the serialization/deserialization:
public class PolymorphicListConverter<TListType>
: PolymorphicListConverter where TListType : class, IList
{
public PolymorphicListConverter()
: base(typeof (TListType))
{
}
}
public class PolymorphicListConverter : JsonConverter
{
private readonly Type _explicitListType;
public PolymorphicListConverter()
{
}
protected PolymorphicListConverter(Type type)
{
_explicitListType = type;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
writer.WriteStartArray();
foreach (var item in (IEnumerable) value)
{
writer.WriteStartObject();
writer.WritePropertyName("CrlType");
writer.WriteValue(item.GetType().AssemblyQualifiedName);
writer.WritePropertyName("Value");
serializer.Serialize(writer, item);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue,
JsonSerializer serializer)
{
var list = (IList) Activator.CreateInstance(
_explicitListType ?? objectType);
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray)
break;
reader.Read(); //CrlType prop name
reader.Read(); //actual type
var type = Type.GetType((string) reader.Value);
reader.Read(); // value property
reader.Read(); // actual value
list.Add(serializer.Deserialize(reader, type));
reader.Read(); // end object
}
return list;
}
public override bool CanConvert(Type objectType)
{
var deserializationType = (_explicitListType ?? objectType);
return typeof (IEnumerable).IsAssignableFrom(objectType) &&
deserializationType.IsClass &&
!deserializationType.IsAbstract;
}
}