C# 反射 - 从 JSON ContractResolver 中的 属性 值获取对象值
C# reflection - get object value from property value within JSON ContractResolver
我有一个 class PersonDto
,其中包含一个 属性 类型 AddressDto
的实例。我正在构建一个名为 eg 的自定义 ContractResolver
。 ShouldSerializeContractResolver
与 Newtonsoft.Json
编组 .NET lib,它将仅包含特定属性到序列化中,这些属性用我的自定义属性标记,例如。 [ShouldSerialize]
当解析器的 CreateProperty
方法进入 PersonDto
即的复杂/自定义类型时会出现问题。它进入 AddressDto
并且不知道 属性 实例被标记为 [ShouldSerialize]
属性。生成的序列化看起来像 "Address": {}
而不是 "Address": { "StreetNumber": 123 }
代码如下:
class AddressDto
{
// PROBLEM 1/2: value does not get serialized, but I want it serialized as its property is [ShouldSerialize] attr tagged
public int StreetNumber { get; set; }
}
class PersonDto
{
public string Name { get; set; } // should not serialize as has not attr on it
[ShouldSerialize]
public string Id { get; set; }
[ShouldSerialize]
public AddressDto Address { get; set; }
}
// JSON contract resolver:
public class ShouldSerializeContractResolver: DefaultContractResolver
{
public ShouldSerializeContractResolver() { }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute<ShouldSerializeContractResolver>(inherit: false);
// PROBLEM 2/2: here I need the code to access the member.DeclaringType instance somehow and then
// find its AddressDto property and its GetCustomAttribute<ShouldSerializeContractResolver>
if (attr is null)
{
property.ShouldSerialize = instance => { return false; };
}
return property;
}
}
// code invoked as:
PersonDto somePerson = IrrelevantSomePersonCreateNewFactoryFn();
var jsonSettings = new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver() };
var strJson = JsonConvert.SerializeObject(somePerson, jsonSettings);
序列化器以“平面”模式工作,即。它通过解析器遍历所有道具,到达成员 StreetNumber
的地步,我不知道如何从中访问“父”MemberInfo,这会很棒。
我发现这里的核心问题是我没有“parent”/DeclaringType 对象实例,需要找到获取它的方法。
请注意,我无法通过[JsonProperty]
、[JsonIgnore]
等方式解决这个问题,因为我的属性很复杂,涉及到它自己的逻辑。
您希望 AddressDto
以不同方式序列化,具体取决于它是否通过标有 [ShouldSerialize]
的 属性 遇到,但是使用自定义合同解析器无法轻松完成,因为Json.NET 为每种类型创建一个合约 无论它在序列化图中的什么地方遇到 。 IE。合同解析器将为 AddressDto
为以下两种数据模型生成相同的合同:
class PersonDto
{
public string Name { get; set; } // should not serialize as has not attr on it
[ShouldSerialize]
public string Id { get; set; }
[ShouldSerialize]
public AddressDto Address { get; set; } // This and its properties should get serialized.
}
class SomeOtherDto
{
[ShouldSerialize]
public string SomeOtherValue { get; set; }
public AddressDto SecretAddress { get; set; } // Should not get serialized.
}
这就是为什么在为引用类型创建属性时无法获取引用 属性 的属性。
相反,您需要在运行时跟踪序列化程序何时开始和结束 [ShouldSerialize]
属性 的序列化,并在内部设置一些 thread-safe 状态变量。这可以做到,例如通过使用您的合同解析器注入自定义 JsonConverter 来设置必要的状态,暂时禁用自身以防止递归调用,然后进行默认序列化:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class ShouldSerializeAttribute : System.Attribute
{
}
public class ShouldSerializeContractResolver: DefaultContractResolver
{
static ThreadLocal<bool> inShouldSerialize = new (() => false);
static bool InShouldSerialize { get => inShouldSerialize.Value; set => inShouldSerialize.Value = value; }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute<ShouldSerializeAttribute>(inherit: false);
if (attr is null)
{
var old = property.ShouldSerialize;
property.ShouldSerialize = instance => InShouldSerialize && (old == null || old(instance));
}
else
{
var old = property.Converter;
if (old == null)
property.Converter = new InShouldSerializeConverter();
else
property.Converter = new InShouldSerializeConverterDecorator(old);
}
return property;
}
class InShouldSerializeConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var old = InShouldSerialize;
try
{
InShouldSerialize = true;
serializer.Serialize(writer, value);
}
finally
{
InShouldSerialize = old;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override bool CanRead => false;
public override bool CanConvert(Type objectType) => throw new NotImplementedException();
}
class InShouldSerializeConverterDecorator : JsonConverter
{
readonly JsonConverter innerConverter;
public InShouldSerializeConverterDecorator(JsonConverter innerConverter) => this.innerConverter = innerConverter ?? throw new ArgumentNullException(nameof(innerConverter));
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var old = InShouldSerialize;
try
{
InShouldSerialize = true;
if (innerConverter.CanWrite)
innerConverter.WriteJson(writer, value, serializer);
else
serializer.Serialize(writer, value);
}
finally
{
InShouldSerialize = old;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var old = InShouldSerialize;
try
{
InShouldSerialize = true;
if (innerConverter.CanRead)
return innerConverter.ReadJson(reader, objectType, existingValue, serializer);
else
return serializer.Deserialize(reader, objectType);
}
finally
{
InShouldSerialize = old;
}
}
public override bool CanConvert(Type objectType) => throw new NotImplementedException();
}
}
然后序列化如下:
IContractResolver resolver = new ShouldSerializeContractResolver(); // Cache statically & reuse for best performance
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(person, Formatting.Indented, settings);
备注:
Newtonsoft 建议 caching and reusing your contract resolver 以获得最佳性能。
上面的实现有一个限制,如果你的[ShouldSerialize]
也标有JsonPropertyAttribute
, fields that control serialization of the property value such as ItemConverterType
and IsReference
将被忽略。
演示 fiddle here.
我有一个 class PersonDto
,其中包含一个 属性 类型 AddressDto
的实例。我正在构建一个名为 eg 的自定义 ContractResolver
。 ShouldSerializeContractResolver
与 Newtonsoft.Json
编组 .NET lib,它将仅包含特定属性到序列化中,这些属性用我的自定义属性标记,例如。 [ShouldSerialize]
当解析器的 CreateProperty
方法进入 PersonDto
即的复杂/自定义类型时会出现问题。它进入 AddressDto
并且不知道 属性 实例被标记为 [ShouldSerialize]
属性。生成的序列化看起来像 "Address": {}
而不是 "Address": { "StreetNumber": 123 }
代码如下:
class AddressDto
{
// PROBLEM 1/2: value does not get serialized, but I want it serialized as its property is [ShouldSerialize] attr tagged
public int StreetNumber { get; set; }
}
class PersonDto
{
public string Name { get; set; } // should not serialize as has not attr on it
[ShouldSerialize]
public string Id { get; set; }
[ShouldSerialize]
public AddressDto Address { get; set; }
}
// JSON contract resolver:
public class ShouldSerializeContractResolver: DefaultContractResolver
{
public ShouldSerializeContractResolver() { }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute<ShouldSerializeContractResolver>(inherit: false);
// PROBLEM 2/2: here I need the code to access the member.DeclaringType instance somehow and then
// find its AddressDto property and its GetCustomAttribute<ShouldSerializeContractResolver>
if (attr is null)
{
property.ShouldSerialize = instance => { return false; };
}
return property;
}
}
// code invoked as:
PersonDto somePerson = IrrelevantSomePersonCreateNewFactoryFn();
var jsonSettings = new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver() };
var strJson = JsonConvert.SerializeObject(somePerson, jsonSettings);
序列化器以“平面”模式工作,即。它通过解析器遍历所有道具,到达成员 StreetNumber
的地步,我不知道如何从中访问“父”MemberInfo,这会很棒。
我发现这里的核心问题是我没有“parent”/DeclaringType 对象实例,需要找到获取它的方法。
请注意,我无法通过[JsonProperty]
、[JsonIgnore]
等方式解决这个问题,因为我的属性很复杂,涉及到它自己的逻辑。
您希望 AddressDto
以不同方式序列化,具体取决于它是否通过标有 [ShouldSerialize]
的 属性 遇到,但是使用自定义合同解析器无法轻松完成,因为Json.NET 为每种类型创建一个合约 无论它在序列化图中的什么地方遇到 。 IE。合同解析器将为 AddressDto
为以下两种数据模型生成相同的合同:
class PersonDto
{
public string Name { get; set; } // should not serialize as has not attr on it
[ShouldSerialize]
public string Id { get; set; }
[ShouldSerialize]
public AddressDto Address { get; set; } // This and its properties should get serialized.
}
class SomeOtherDto
{
[ShouldSerialize]
public string SomeOtherValue { get; set; }
public AddressDto SecretAddress { get; set; } // Should not get serialized.
}
这就是为什么在为引用类型创建属性时无法获取引用 属性 的属性。
相反,您需要在运行时跟踪序列化程序何时开始和结束 [ShouldSerialize]
属性 的序列化,并在内部设置一些 thread-safe 状态变量。这可以做到,例如通过使用您的合同解析器注入自定义 JsonConverter 来设置必要的状态,暂时禁用自身以防止递归调用,然后进行默认序列化:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class ShouldSerializeAttribute : System.Attribute
{
}
public class ShouldSerializeContractResolver: DefaultContractResolver
{
static ThreadLocal<bool> inShouldSerialize = new (() => false);
static bool InShouldSerialize { get => inShouldSerialize.Value; set => inShouldSerialize.Value = value; }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute<ShouldSerializeAttribute>(inherit: false);
if (attr is null)
{
var old = property.ShouldSerialize;
property.ShouldSerialize = instance => InShouldSerialize && (old == null || old(instance));
}
else
{
var old = property.Converter;
if (old == null)
property.Converter = new InShouldSerializeConverter();
else
property.Converter = new InShouldSerializeConverterDecorator(old);
}
return property;
}
class InShouldSerializeConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var old = InShouldSerialize;
try
{
InShouldSerialize = true;
serializer.Serialize(writer, value);
}
finally
{
InShouldSerialize = old;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override bool CanRead => false;
public override bool CanConvert(Type objectType) => throw new NotImplementedException();
}
class InShouldSerializeConverterDecorator : JsonConverter
{
readonly JsonConverter innerConverter;
public InShouldSerializeConverterDecorator(JsonConverter innerConverter) => this.innerConverter = innerConverter ?? throw new ArgumentNullException(nameof(innerConverter));
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var old = InShouldSerialize;
try
{
InShouldSerialize = true;
if (innerConverter.CanWrite)
innerConverter.WriteJson(writer, value, serializer);
else
serializer.Serialize(writer, value);
}
finally
{
InShouldSerialize = old;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var old = InShouldSerialize;
try
{
InShouldSerialize = true;
if (innerConverter.CanRead)
return innerConverter.ReadJson(reader, objectType, existingValue, serializer);
else
return serializer.Deserialize(reader, objectType);
}
finally
{
InShouldSerialize = old;
}
}
public override bool CanConvert(Type objectType) => throw new NotImplementedException();
}
}
然后序列化如下:
IContractResolver resolver = new ShouldSerializeContractResolver(); // Cache statically & reuse for best performance
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(person, Formatting.Indented, settings);
备注:
Newtonsoft 建议 caching and reusing your contract resolver 以获得最佳性能。
上面的实现有一个限制,如果你的
[ShouldSerialize]
也标有JsonPropertyAttribute
, fields that control serialization of the property value such asItemConverterType
andIsReference
将被忽略。
演示 fiddle here.