c# - Is it possible to set custom (de)serializers for open generic types in ServiceStack.Text? -
i have type this:
class foo<t> { public string text { get; set; } public t nested { get; set; } public static string tojson(foo<t> foo) { [...] } }
tojson
serializes foo<bar>
instance json in way impossible achieve tweaking jsconfig
. also, tojson
relies on servicestack.text serialize nested
, can instance of foo<baz>
.
unfortunately, way jsconfig
implemented implies there jsconfig<t>
set of static variables foo<bar>
, other foo<baz>
. also, afaik, servicestack.text offers no way configure json serialization open generic types (i.e.: jsconfig.add(typeof(foo<>), config)
). tried solve issue creating static constructor foo<t>
:
static foo() { jsconfig<foo<t>>.rawserializefn = tojson; }
this doesn't work time. depends on order static constructors invoked runtime. apparently, servicestack.text caches serializers functions , doing before static constructor called depending on order operations invoked in api, so:
var outer = new foo<baz> { text = "text" }; outer.tojson(); // ok, because nested null var inner = new foo<bar>(); inner.tojson(); // ok, because jsconfig<foo<bar>>.rawserializefn foo<t>.tojson outer.nested = inner; outer.tojson(); // not ok, because ss.text uses default serializer foo<t>, not foo<t>.tojson
i can't set serializers in jsconfig<foo<t>>
beforehand because t can virtually type, other generic types.
is possible define custom serialization routines open generic types (that can nested) in servicestack.text?
i solved in own way wrapper , custom deserializer. created base type of abstract types. base type tells system type is:
public class setsettingitembase { public string key { get; set; } public string valuetype { get; set; } }
so base metadata -- setting key + value type. object dto, then, extends adding actual value:
public class setsettingitem : setsettingitembase { public object value { get; set; } }
note it's object
. dto, not actual object. can cast later, or convert real/generic type after serialization.
my custom serialization is:
jsconfig<setsettingitem>.rawdeserializefn = str => { var basesettings = str.fromjson<setsettingitembase>(); var ret = basesettings.mapto<setsettingitem>(); if(true) // actual condition removed here... unimportant example { var datatype = constants.knownsettingstypes[basesettings.valuetype]; var method = typeof(jsonextensions).getmethod("jsonto").makegenericmethod(datatype); var key = "value"; var parsed = jsonobject.parse(str); if(parsed.object(key) == null) key = "value"; ret.value = method.invoke(null, new object[] { parsed, key }); } return ret; };
this method first deserializes simple base. value
passed in dto ignored when deserializing basesettings
. call mapto
prepare actual setsettingitem
dto. mapto
wrapper around automapper
. use ss's built in mapper here.
for security, have set list of types allow settings. example:
knownsettingstypes.add("string", typeof(string)); knownsettingstypes.add("int", typeof(int)); knownsettingstypes.add("nullableint", typeof(int?)); knownsettingstypes.add("nullablepercentage", typeof(double?)); knownsettingstypes.add("feegrid", typeof(feegrid));
after that, use reflection jsonto method, passing in generic type parameter dynamically knownsettingstypes
dictionary.
and finishing up, parse object using generic jsonobject.parse
method , looking value
or value
(depending on case sensitivity) , explicitly convert jsonobject
using dynamic method
created earlier.
the end result can pass in settings of different types here part of dtos.
this served purposes time being, looking @ example see improving in 2 ways:
- after parsing, convert
setsettingitem
settingitem<t>
use strongly-typed object in code. remember, example dtos across wire. - instead of requiring person pass in
type
me check against, check against settingkey
know type supposed , parse accordingly. in example, if check against master list of settings , types, i'd still require them pass intype
precaution , throw exception if didn't match.
Comments
Post a Comment