gpt4 book ai didi

c# - 基于编译时类型自定义Json.NET序列化

转载 作者:行者123 更新时间:2023-12-04 07:35:27 25 4
gpt4 key购买 nike

考虑以下 Json.NET序列化样式代码:

[JsonConverter(typeof(MyConverter))]
class A {
...
}

[JsonConverter(typeof(MyConverter))]
class B : A {
...
}

class MyConverter {
public override void WriteJson(
JsonWriter writer,
object value,
JsonSerializer serializer) { ... }
}

class CA {
public readonly A _x;
CA(A x) { _x = x; }
}

class CB {
public readonly B _x;
CB(B x) { _x = x; }
}

private static void Main(string[] args) {
B b = new B(...);
CA ca = new CA(b);
CB cb = new CB(b);

string caStr = JsonConvert.SerializeObject(ca);
string cbStr = JsonConvert.SerializeObject(cb);
}
在上面的代码中, CACB序列化为完全相同的字符串 caStrcbStr .但我要 MyConverter了解编译时类型并采取不同的行动。在 CB的情况下,所载 B _x应该只是以默认方式序列化。通过在 CA的情况下, MyConverter应该以某种定义的方式标记它序列化的内容,然后序列化包含的 A _x (其运行时类型为 B )以通常的默认方式 B 被序列化。
所以,我的问题是,可以自定义 JsonConverter接收有关它正在序列化的类型的编译时类型的信息?在这种情况下,调整序列化调用将不起作用,因为我也需要它来处理成员对象,其中我没有直接调用序列化。
请注意,虽然我实际做的类似于 TypeNameHandling它不完全相同,我需要符合外部规范,所以我真的需要这里的自定义行为。

最佳答案

您可以利用应用于属性的转换器优先于其他转换器的事实为 A _x 使用自定义转换器实例。 .
正如其 Serialization Guide 中所述:

JsonConverters can be defined and specified in a number of places: in an attribute on a member, in an attribute on a class, and added to the JsonSerializer's converters collection. The priority of which JsonConverter is used is the JsonConverter defined by attribute on a member, then the JsonConverter defined by an attribute on a class, and finally any converters passed to the JsonSerializer.


这使得可以将声明的类型信息(或任何其他编译时信息)传递给 MyConverter当应用于 A _x通过使用 converter parameters .在你写的评论中,我需要向自定义转换器显示编译时类型,所以如果编译时类型是指 声明类型 A_x 你可以这样做。
修改 MyConverter添加一个带有 declaredType 的构造函数范围:
class MyConverter : JsonConverter 
{
public MyConverter() : this(typeof(A)) { }

public MyConverter(Type declaredType) => this.DeclaredType = declaredType;

public Type DeclaredType { get; init; }

public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer)
{
// This is just a mockup, your question doesn't specify your requirements.
var a = (A)value;
writer.WriteStartObject();
if (DeclaredType == typeof(A) && a is B b)
{
//By in the case of CA, MyConverter should tag what it serialises in some defined way
writer.WritePropertyName("isB");
writer.WriteValue(true);
}
// Add whatever logic you need to serialize A
// Add whatever additional logic you need to serialize B
// And end the object
writer.WriteEndObject();
}

public override bool CanConvert(Type objectType) => typeof(A).IsAssignableFrom(objectType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
}
然后按如下方式修改您的类:
class CA {
[JsonConverter(typeof(MyConverter), typeof(A))]
public readonly A _x;
public CA(A _x) { this._x = _x; } // The constructor argument names must have the same case-invariant name as the corresponding member for the converter to be applied
}

class CB {
[JsonConverter(typeof(MyConverter), typeof(B))]
public readonly B _x;
public CB(B _x) { this._x = _x; }
}

[JsonConverter(typeof(MyConverter))]
class A {
}

[JsonConverter(typeof(MyConverter), typeof(B))]
class B : A {
}
或者 , 你可以做 MyConverter generic在声明的类型中:
class MyConverter<TDeclared> : JsonConverter<TDeclared> where TDeclared : A
{
public override void WriteJson( JsonWriter writer, TDeclared value, JsonSerializer serializer)
{
// This is just a mockup, your question doesn't specify your requirements.
writer.WriteStartObject();
if (typeof(TDeclared) != value.GetType())
{
//By in the case of CA, MyConverter should tag what it serialises in some defined way
writer.WritePropertyName("isB");
writer.WriteValue(value.GetType() == typeof(B));
}
// Add whatever logic you need to serialize A
// Add whatever additional logic you need to serialize B
// And end the object
writer.WriteEndObject();
}
public override TDeclared ReadJson(JsonReader reader, Type objectType, TDeclared existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException();
}
并按如下方式应用它:
class CA {
[JsonConverter(typeof(MyConverter<A>))]
public readonly A _x;
public CA(A _x) { this._x = _x; } // The constructor argument names must have the same case-invariant name as the corresponding member for the converter to be applied
}

class CB {
[JsonConverter(typeof(MyConverter<B>))]
public readonly B _x;
public CB(B _x) { this._x = _x; }
}

[JsonConverter(typeof(MyConverter<A>))]
class A {
}

[JsonConverter(typeof(MyConverter<B>))]
class B : A {
}
无论哪种方式, CA ca将被序列化如下:
{"_x":{"isB":true}}
虽然 CB cb仍然会像这样被序列化:
{"_x":{}}
笔记:
  • 由于 Json.NET 是一个基于契约的序列化器,它在序列化对象时通常不提供有关当前序列化堆栈的信息。父对象决定序列化什么,子对象决定如何序列化自己。因此例如MyConverter不通知容器对象是否为 CA 类型或 CB或者正在写入该容器的特定属性。
    自定义成员转换器是该一般原则的一个显着异常(exception)。
  • 在这两种情况下,我的代码都假定您不想要 B独立序列化时标记。如果您确实希望将其标记为独立,请编辑您的问题以进行澄清。
  • 当使用参数化构造函数反序列化不可变类型时,Json.NET 使用大小写不变匹配将参数名称与属性匹配,以便使用查找和使用 [JsonProperty][JsonConverter]可以应用于属性的属性。因此,我将您的构造函数参数名称修改为 _x确保MyConverter在反序列化期间应用。

  • 演示 fiddle #1 here对于参数化转换器,以及 #2 here对于通用版本。

    关于c# - 基于编译时类型自定义Json.NET序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67775174/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com