pondělí 21. října 2013

C# JSON Serializace a deserializace polymorfních objektů

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\"}]"