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

4
我有一个类PersonDto,其中包含类型为AddressDto的属性实例。 我正在构建一个自定义的ContractResolver,命名为例如ShouldSerializeContractResolver,使用Newtonsoft.Json编组.NET库,它将仅包括标记有我的自定义属性[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,这将非常有用。

enter image description here

我认为这里的核心问题是我没有“父”/DeclaringType对象实例,并需要找到一种获取它的方法。
请注意,我无法通过[JsonProperty]、[JsonIgnore]等解决此问题,因为我的属性是复杂的,涉及其自己的逻辑。

简单的解决方法是使用[ShouldSerialize]标记AddressDto子类型的属性,但这不是我想要的... - Vedran Mandić
1
如果你重写CreateProperties并从该调用中返回JsonProoerty作为集合,那不是更容易吗?你仍然需要在解析器内保持状态,但看起来你在该调用签名中已经拥有了所需的类型。这还没有经过测试,只是给你提供一些想法。 - rene
呼,哈哈,这个很不错。为了跟踪状态,目前我真的看不出其他方法,但这种魔法可能有效。我需要在每次运行(级别)时保留记录,然后在更深入时检查保存的“父级”。 - Vedran Mandić
1
你不能轻易地做你想要的事情,因为Json.NET是一种基于合同的序列化程序,它为每个类型创建一个合同,无论在序列化图中遇到它在哪里。你希望AddressDto根据是否通过标记为[ShouldSerialize]的属性遇到而以不同的方式进行序列化,但它并没有被设计成这样。你需要做一些更复杂的事情,比如注入一个转换器,为每个标记为[ShouldSerialize]的值切换到不同的序列化程序/合同解析器。这额外的努力值得吗? - dbc
那肯定很难听。但是谢谢你的想法。目前仍然坚持为子类型分配显式属性赋值。我可能会尝试根据 @rene 的建议实现一种状态机制。 - Vedran Mandić
1个回答

1
您希望根据是否通过标有[ShouldSerialize]的属性遇到它来以不同方式序列化AddressDto,但是使用自定义合同解析器很难做到这一点,因为无论在序列化图中遇到类型的位置如何,Json.NET都会创建一个完全相同的合同。也就是说,合同解析器将为以下两个数据模型生成相同的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] 属性,并在其中设置一些线程安全状态变量。可以通过使用您的合同解析器来注入自定义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 在这里


1
它确实解决了原始问题,所以我将其标记为答案,谢谢。 - Vedran Mandić

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接