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

pátek 22. března 2013

Věčný problém s parsováním parametrů URL

Opakovaně řeším v serverové službě problém s parsováním parametrů.


  1. Když jsem si službu napsal jako WCF REST, tak jsem zjistil, že WCF neumí pracovat s dynamickým počtem parametrů. Tak jsem dynamické parametry zakódoval do stringu. Vše se zdálo v pohodě až do chvíle, kdy se v parametrech objevilo '/' nedokáže WCF příjmout HTTP request. Vadí mu lomítko navíc. Zde jsem řešení nenašel
  2. Když jsem službu napsal jako HttpListener, tedy pouze jednoduchý zachytávač HTTP requestů, tak jsem narazil na podobný problém se znakem '&'.

    Parametry jsem si naparsoval pomocí utility:
    NameValueCollection qscoll = HttpUtility.ParseQueryString(querystring);
    http://localhost:8128/Action/ActionExecute/command=c:/notepad&.exe
    Dostal jsem výsledek:
    0) command=c:\notepad
    1) null = .exe
    Jak z toho ven (sto hoven)?
Nakonec jsem napsal dotaz na stackoverflow:
Parse Query string

Řešení je velmi jednoduché. Musíte dostat korektní QueryString, který má nahrazené řídící znaky escape sekvencí.

pondělí 18. března 2013

Kroutím hlavou

string stanice="frekvence1";

Public string get()
{
string stanice="kiss";
return stanice;
}

čtvrtek 7. března 2013

Window Manager

Přemýšlím, jak do aplikačního frameworku MEF, Prism začlenit Windows manager


Inspirace:
http://www.codeproject.com/Articles/249261/How-To-Embed-An-Application-Into-a-Docking-Library


pátek 23. listopadu 2012

WCF ROUTING


                http://msdn.microsoft.com/en-us/library/ee517421.aspx

čtvrtek 8. března 2012

WCF - RESTFULL - service - webová služba

Do našeho serveru budeme přidávat dolaší WCF službu. Tentokráte se bude jednat o služby přístupnou přes HTTP  protokol. Jedná se o klasickou webovou službu.

Podívejme se na příklad:
Vytvoříme si nový ServiceContract.
[ServiceContract]
public interface IEventService
{          
        
  [OperationContract]
  [WebGet(UriTemplate = "EventInfo/{exe_name}", ResponseFormat = WebMessageFormat.Xml)]
  EventInfo GetEventInfo(string exe_name);

}
Způsob volání přes protokol HTTP se konfiguruje pomocí atributu WebGet. Bude se jednat o volání přes HTTP příkaz GET. Kdybychom chtěli využít jiných HTTP příkazů, můžeme využít atributu WebInvoke pro jejich konfiguraci.
[OperationContract]
[WebInvoke(Method = "PUT", UriTemplate = "/customer/{id}")]
public void PutCustomer(int id, Customer customer)
{
   
}
 
[OperationContract]
[WebInvoke(Method = "DELETE", UriTemplate = "/customer/{id}")]
public void DeleteCustomer(int id)
{
  
        
}
Podívejme zpět se na první příklad, kde funkce GetEventInfo vrací třídu EventInfo. Zajisté vás zajímá, v jakém formátu se vrátí odpověď od serveru. Formát si můžete přepnout buď přepínačem ResponseFormat = WebMessageFormat.Xml nebo přepínačem ResponseFormat = WebMessageFormat.Json. Zaměřme se na XML formát. Mám jednoduchou třídu:
[DataContract]    
public class EventInfo
{   
  [DataMember]
  public string Name
  {
    get;            
    set;            
  }
}
Server mi vrátí XML a názvy tagů jsou totožné z názvy tříd. Server vrátí výsledné XML jako:
<EventInfo>
<Name>AKCE</Name>
</EventInfo>
To je dobré, ale často mohu potřebovat pojmenovat tagy nějakým vlastním jménem. Jakým způsobem mohu ovlivnit názvy tagů?
[DataContract(Name="MyEventInfo")]    
public class EventInfo
{   
  [DataMember(Name="Jmeno")]
  public string Name
  {
    get;            
    set;            
  }
}

Pokud do atributu DataContract přidáte parametr Name, dostane tag ve výstupním XML toto jméno. To samé se stane, když přidáte parametr Name do atributu DataMember.Ze serveru se mi vrátí XML:
<MyEventInfo xmlns="http://schemas.datacontract.org/2004/07/WCF8bcPgServices" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Jmeno>AKCE</Jmeno>
</MyEventInfo>
To je vše přátelé.