当JSON属性值是双序列化转义JSON字符串时,如何自动反序列化该属性值?

s3fp2yjn  于 2023-02-14  发布在  其他
关注(0)|答案(1)|浏览(155)

假设我有以下类型:

class Foo<T>{
public T data {get;init;}
}

我想在多个控制器中使用这种类型:

[HttpPost]
public async void Post(Foo<SomeComplexClass> myFoo){
myFoo.data // this is SomeComplexClass
// Dostuff
}

[HttpPost]
public async void Post2(Foo<OtherMoreDifferentClass>foo2){
foo2.data // this is OtherMoreDifferentClass
}

这一切都很好,除了这个API的调用者(我无法控制)在发送http请求之前将 * serialize * T转换为json,因此所有内容都以Foo<string>的形式进入。我正在考虑为Foo编写一个转换器,该转换器能够接收此字符串,并通过在JsonConverter中将其反序列化为请求的类型来自动将其转换为请求的类型。
我希望不必重写控制器或将序列化代码放入控制器
问题是JsonConverter<T>类要求你使用utf8jsonreader和writer来实现读写,这是System.Text.Json库中最低层的apis。它们处理单独的标记!我所要做的就是获取整个字符串,将其序列化为Foo<string>,然后获取www.example.com并重新-foo.data and re-deserialize it into the requested type if that makes any sense.
有没有什么办法可以用这个utf8jsonreader换一个更高级别的api?或者这个转换器业务是解决这个问题的错误方法?

    • 更新**

为了澄清,假设我有以下类:

public class SomeComplexClass
{
    public string Foo { get; set; } = "";
}

一般情况下,应按如下顺序进行序列化:

{"data":{"Foo":"hello"}}

但是我的API的调用者正在发送以下内容:

{"data":"{\"Foo\":\"hello\"}"}

正如您所看到的,data的值被双重序列化了:一次是字符串,第二次是在通过网络序列化容器对象时。我希望有一些自动的方法将嵌入的双序列化JSON绑定到我的Foo<T>.data值。

lrpiutwd

lrpiutwd1#

您可以创建一个custom JsonConverter,在Read()中检查当前JSON令牌是否为字符串,如果是,则将该字符串反序列化为字符串,然后将该字符串反序列化为最终的数据模型T

class Foo<T>
{
    [JsonConverter(typeof(DoubleSerializedConverter))]
    public T data {get; init;}
}

public class DoubleSerializedConverter : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert) => true;

    class ObjectOrArrayKindConverter<T> : JsonConverter<T>
    {
        public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
            reader.TokenType switch
            {
                JsonTokenType.String => JsonSerializer.Deserialize<T>(reader.GetString()!, options),
                _ => JsonSerializer.Deserialize<T>(ref reader, options),
            };
        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, options);
    }
    
    class UnknownKindConverter<T> : JsonConverter<T>
    {
        // TODO: decide how to handle primitive types like string, byte [] and so on.  There's no foolproof way to check whether they were conditionally double serialized,
        // so you must either assume they were always double-serialized, or assume they were never double-serialized.
        //public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => JsonSerializer.Deserialize<T>(ref reader, options);
        public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => 
            reader.TokenType switch
            {
                JsonTokenType.String => JsonSerializer.Deserialize<T>(reader.GetString()!, options), // Use if you want to assume strings and other primitives are double serialized.
                //JsonTokenType.String => JsonSerializer.Deserialize<T>(ref reader, options),  // Use if you want to assume strings and other primitives are not double serialized.
                _ => JsonSerializer.Deserialize<T>(ref reader, options),
            };
        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, options);
    }

    public sealed override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        var typeInfo = JsonSerializerOptions.Default.GetTypeInfo(typeToConvert);
        var genericConverterType = (typeInfo.Kind == JsonTypeInfoKind.None ? typeof(UnknownKindConverter<>) : typeof(ObjectOrArrayKindConverter<>));
        return (JsonConverter)Activator.CreateInstance(genericConverterType.MakeGenericType(typeToConvert))!;
    }
}

注:

  • 可以可靠地检查通常被序列化为JSON对象或数组的POCO是否被双重序列化,但不可能可靠地检查. NET原语是否被双重序列化。例如,原始JSON字符串"\"hello\""可能是双重序列化的hello或单一序列化的"hello"

因此,您需要决定如何处理原语。上面的转换器假设原语是双序列化的,但是如果您希望相反,请在上面的UnknownKindConverter<T>中取消注解

//JsonTokenType.String => JsonSerializer.Deserialize<T>(ref reader, options)`
  • JsonSerializerOptions.GetTypeInfo(Type)用于确定T data是否正常序列化为JSON对象或数组。此API是在. NET 7中引入的,因此如果您使用的是早期版本,则可能需要将该逻辑替换为手工创建的内容。

演示小提琴here

相关问题