是否可以在ServiceStack.Text中为开放泛型类型设置自定义(反)序列化器?

时间:2014-05-16 22:46:45

标签: c# json generics serialization servicestack-text

我有这样的类型:

class Foo<T>
{
  public string Text { get; set; }

  public T Nested { get; set; }

  public static string ToJson(Foo<T> foo) { [...] }
}

ToJson以一种无法通过调整Foo<Bar>无法实现的方式将JsConfig实例序列化为JSON。此外,ToJson依赖ServiceStack.Text来序列化NestedFoo<Baz>可以是JsConfig的实例。

不幸的是,实施JsConfig<T>的方式意味着Foo<Bar>的{​​{1}}静态变量和Foo<Baz>的其他静态变量都有JsConfig.Add(typeof(Foo<>), config)个。此外,AFAIK,ServiceStack.Text无法为开放泛型类型配置JSON序列化(即:Foo<T>之类的东西)。我试图通过为static Foo() { JsConfig<Foo<T>>.RawSerializeFn = ToJson; }

创建这个静态构造函数来解决这个问题
var outer = new Foo<Baz> { Text = "text" };
outer.ToJson(); // OK, because Nested is null

var inner = new Foo<Bar>();
inner.ToJson(); // OK, because JsConfig<Foo<Bar>>.RawSerializeFn is Foo<T>.ToJson

outer.Nested = inner;
outer.ToJson(); // NOT OK, because SS.Text uses the default serializer for Foo<T>, not Foo<T>.ToJson

这并不是一直有效。它取决于运行时调用静态构造函数的顺序。显然,ServiceStack.Text缓存序列化函数,有时在调用静态构造函数之前执行此操作,具体取决于API中调用的顺序操作,因此:

JsConfig<Foo<T>>

我无法预先在{{1}}中设置所有序列化程序,因为T几乎可以是任何类型,甚至是其他类型的。

是否可以在ServiceStack.Text中为开放泛型类型(可以嵌套)定义自定义序列化例程?

1 个答案:

答案 0 :(得分:2)

我用自己的方式用包装器和自定义反序列化器解决了这个问题。我为所有抽象类型创建了一个基类型。该基类型告诉系统它是类型

public class SetSettingItemBase
{
    public string Key { get; set; }
    public string ValueType { get; set; }
}

所以基础本质上是元数据 - 设置键+值类型。然后,对象DTO通过添加实际值来扩展它:

public class SetSettingItem : SetSettingItemBase
{
    public object Value { get; set; }
}

请注意,它只是object。这是DTO,而不是我的实际对象。我可以稍后再使用它,或者在序列化后将其转换为真实/通用类型。

我的自定义序列化是:

JsConfig<SetSettingItem>.RawDeserializeFn = str =>
            {
                var baseSettings = str.FromJson<SetSettingItemBase>();
                var ret = baseSettings.MapTo<SetSettingItem>();

                if(true) // actual condition removed here... unimportant to the 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;
            };

此方法首先反序列化为简单基数。因此,在反序列化Value时,将忽略从DTO传入的baseSettings。然后我致电MapTo准备实际的SetSettingItem DTO。 MapTo只是AutoMapper的封套。您可以在这里轻松使用SS内置的映射器。

为了安全起见,我有一组我允许作为设置的类型列表。示例:

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));

之后,我使用反射来获取JsonTo方法,从KnownSettingsTypes字典动态传递泛型类型参数。

然后完成所有操作,我使用通用JsonObject.Parse方法解析对象,然后查找Valuevalue(取决于区分大小写)并显式转换{ {1}}使用我之前创建的动态JsonObject

最终结果是我可以在这里传递所有不同类型的设置,作为我的DTO的一部分。

这暂时满足了我的目的,但看看这个例子我可以看到它有两种改进:

  1. 解析后,我可以将method转换为SetSettingItem,这样我就可以在代码中将其用作强类型对象。请记住,这个例子只是让DTO通过网络获得它。
  2. 我可以检查设置SettingItem<T>以确定它应该是哪种类型并进行相应的解析,而不是要求此人传递type以供我检查。在我的示例中,即使我检查设置的主列表及其类型,我仍然可能要求他们传递Key作为预防措施,并且如果他们没有这样做则会抛出异常。 t match。