C# 反射 - 从 JSON ContractResolver 中的 属性 值获取对象值

C# reflection - get object value from property value within JSON ContractResolver

我有一个 class PersonDto,其中包含一个 属性 类型 AddressDto 的实例。我正在构建一个名为 eg 的自定义 ContractResolverShouldSerializeContractResolverNewtonsoft.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);

备注:

演示 fiddle here.