C# JSON Serializace a deserializace polymorfních objektů -rozepsané
Po dlouhé době jsem narazil na zajímavý problém. Potřebuji uložit data do formátu JSON ale každá datová položka je jiný typ. Jedná se o tzv. polymofní typ. Například chci si uložit databázi zvířat (animals) a mám definovaný obecný objekt animal a pak mám definované konkrétní typy Cat, Lion, Horse. A každý typ má trochu jiné vlastnosti.
Podívejme se na můj konkrétní případ: Mám pole nějakých objektů, které představují nějaké nastavení. Každá položka v poli má své jméno Name a hodnotu Value.
public class OptionValue
{
[DataMember]
public string Name { get; set; }
[DataMember]
public object Value { get; set; }
}
Defaultní JSON serializér mi udělal jednoduchý JSON, ale nešlo rozeznat Typy Value. Udělal jsem si pro každý datový typ speciální variantu:
[DataContract]
public class IntValue
{
public IntValue()
{
}
public IntValue(string name)
{
Name = name;
}
[DataMember]
public string Name { get; set; }
[DataMember]
public new Int32 Value { get; set; }
}
[DataContract]
public class BoolValue
{
public BoolValue()
{
}
public BoolValue(string name)
{
Name = name;
}
[DataMember]
public string Name { get; set; }
[DataMember]
public new bool Value { get; set; }
}
[DataContract]
public class StringValue
{
public StringValue()
{
}
public StringValue(string name)
{
Name = name;
}
[DataMember]
public string Name { get; set; }
[DataMember]
public new String Value { get; set; }
}
[DataContract]
public class TimeValue
{
public TimeValue()
{
}
public TimeValue(string name)
{
Name = name;
}
[DataMember]
public string Name { get; set; }
[DataMember]
public new TimeSpan Value { get; set; }
}
[DataContract]
public class DateValue
{
public DateValue()
{
}
public DateValue(string name)
{
Name = name;
}
[DataMember]
public string Name { get; set; }
[DataMember]
public new DateTime Value { get; set; }
}
Pokud použiji defaultní serializér:
msg = Request.CreateResponse(HttpStatusCode.OK,array, "application/json");
dostanu výsledek, kde nevím, jakého typu mám položky.
[{"Name":"DefaultSection","Value":"SatelliteHolder"},{"Name":"FillInterfaceName","Value":"Bonus"},{"Name":"FillFilterName","Value":"SATELLITE FILL"},{"Name":"FillSection:Position","Value":"end"},{"Name":"FillSection:Position","Value":"end"},{"Name":"FillSection:Position","Value":"end"},{"Name":"ConfigFillSectionType","Value":"SpotsetHolder"},{"Name":"MinAllowFillDistance","Value":240},{"Name":"MinGroupFillDistance","Value":10},{"Name":"SeparationFilterName","Value":"SATELLITE SEPARATION"},{"Name":"FillFilterSkip","Value":"SATELLITE SKIP"},{"Name":"ConfigSkipSectionType","Value":"SpotsetHolder"},{"Name":"Performance","Value":false},{"Name":"VisibleSections","Value":"HourHeader|SatelliteHolder"}]
Na rozlišení typů jsem si našel jiný serializer, který se nacháyí v System.Web.Extensions.
string json = new JavaScriptSerializer(new TypeResolver()).Serialize(o);
msg = Request.CreateResponse(HttpStatusCode.OK,json, "application/json");
K tomu musíte ješte připsat JavaScriptTypeResolver:
public class TypeResolver : JavaScriptTypeResolver
{
public override Type ResolveType(string id)
{
if (id == "string")
{
return new StringValue().GetType();
}
else if (id == "int")
{
return new IntValue().GetType();
}
else if (id == "bool")
{
return new BoolValue().GetType();
}
else if (id == "float")
{
return new FloatValue().GetType();
}
else if (id == "time")
{
return new TimeSpan().GetType();
}
else if (id == "date")
{
return new DateTime().GetType();
}
return new object().GetType();
}
public override string ResolveTypeId(Type type)
{
if (type.Name == "StringValue")
return "string";
else if (type.Name == "IntValue")
return "int";
else if (type.Name == "BoolValue")
return "bool";
else if (type.Name == "FloatValue")
return "float";
else if (type.Name == "TimeValue")
return "time";
else if (type.Name == "DateValue")
return "date";
return "anyType";
}
Zpětně jde výsledek zase serializovat:
object obj = new JavaScriptSerializer(new TypeResolver()).Deserialize<Options>(json);
Výsledek už nevypadá tak hezky, ale informaci o typu zde již máme.
"[{\"__type\":\"string\",\"Name\":\"DefaultSection\",\"Value\":\"SatelliteHolder\"},{\"__type\":\"string\",\"Name\":\"FillInterfaceName\",\"Value\":\"Bonus\"},{\"__type\":\"string\",\"Name\":\"FillFilterName\",\"Value\":\"SATELLITE FILL\"},{\"__type\":\"string\",\"Name\":\"FillSection:Position\",\"Value\":\"end\"},{\"__type\":\"string\",\"Name\":\"FillSection:Position\",\"Value\":\"end\"},{\"__type\":\"string\",\"Name\":\"FillSection:Position\",\"Value\":\"end\"},{\"__type\":\"string\",\"Name\":\"ConfigFillSectionType\",\"Value\":\"SpotsetHolder\"},{\"__type\":\"int\",\"Name\":\"MinAllowFillDistance\",\"Value\":240},{\"__type\":\"int\",\"Name\":\"MinGroupFillDistance\",\"Value\":10},{\"__type\":\"string\",\"Name\":\"SeparationFilterName\",\"Value\":\"SATELLITE SEPARATION\"},{\"__type\":\"string\",\"Name\":\"FillFilterSkip\",\"Value\":\"SATELLITE SKIP\"},{\"__type\":\"string\",\"Name\":\"ConfigSkipSectionType\",\"Value\":\"SpotsetHolder\"},{\"__type\":\"bool\",\"Name\":\"Performance\",\"Value\":false},{\"__type\":\"string\",\"Name\":\"VisibleSections\",\"Value\":\"HourHeader|SatelliteHolder\"}]"