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
setsettingitemsettingitem<t>use strongly-typed object in code. remember, example dtos across wire. - instead of requiring person pass in
typeme check against, check against settingkeyknow type supposed , parse accordingly. in example, if check against master list of settings , types, i'd still require them pass intypeprecaution , throw exception if didn't match.
Comments
Post a Comment