如何在 json 序列化时使用 DefaultContractResolver 覆盖具有字符串值的复杂类型属性

Posted

技术标签:

【中文标题】如何在 json 序列化时使用 DefaultContractResolver 覆盖具有字符串值的复杂类型属性【英文标题】:How to override complex type property with string value using DefaultContractResolver while json serialization 【发布时间】:2021-08-25 19:23:57 【问题描述】:

我有一个具有多个属性的类,其中一些属性是复杂类型,而它本身具有自己的多个属性,我正在寻找一种方法来仅从复杂类型中序列化一个属性。

例如我有这 3 个类

class Organization

    public Int32 ID get; set;
    public string Name get; set;
    public Geography Location get; set;


class Geography 

    public Int32 GeoID get; set;
    public string Country get; set;
    public string City get; set;
    public string State get; set;
   

在这里我想序列化组织类对象,它应该只使用属性“位置”中的“国家”以及其他属性,我期望的 json 字符串输出如下,使用 NewtonSoft 库中的 JsonConvert.SerializeObject 方法。


   "ID":1,
   "Name":"Sales",
   "Location":"India"

我不想将匿名类型用作可序列化对象,因为我需要为每个使用此属性类型进行消毒的对象保持通用。

我正在尝试使用DefaultContractResolver,因为我已经实现了从序列化中排除选定属性的实现,在相同的实现中我尝试通过覆盖CreateProperty 方法但它在转换中失败,我也无法获得要设置的值新创建的属性,如下所示。这只是一个尝试我不知道这是否是用于所需功能的正确方法!请提出建议。

protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)

    var property = base.CreateProperty(member, memberSerialization);
    JsonProperty newproperty = property;

    if (property.PropertyType == typeof(Geography))
    
        newproperty = new JsonProperty()
        
            PropertyName = property.PropertyName,
            PropertyType = typeof(string)
            //How to get the value from parent property to set with newproperty? 
        ;
    
    return newproperty;

【问题讨论】:

1) 当 [custom JsonConverter for Location 可能是一个更简单的选择时,为什么要使用合约解析器来执行此操作? 2)您需要序列化还是同时序列化和反序列化 @dcb 我只需要序列化对象,我已经实现了一些常见的功能,可以在使用合约解析器进行序列化时跳过和更改属性名称,因此也试图实现这个要求。我的问题是,我无法在实现的解决方案中传递自定义匿名对象。如果不使用匿名类型,您指的是cutom JsonConverter,请建议一些示例。 【参考方案1】:

我为上述功能构建了这个解决方案,一切都按照我的预期工作,但不确定这是不是正确的方法。

合同解决程序

public class DynamicContractResolver : DefaultContractResolver

    private readonly Dictionary<Type, HashSet<string>> _serializableProperties;

    public DynamicContractResolver()
    
        _serializableProperties = new Dictionary<Type, HashSet<string>>();
    

    public void SerializableProperty(Type type, params string[] jsonPropertyNames)
    
        if (!_serializableProperties.ContainsKey(type))
            _serializableProperties[type] = new HashSet<string>();

        foreach (var prop in jsonPropertyNames)
            _serializableProperties[type].Add(prop);
    


    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    
        IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);

        if (_serializableProperties.Any())
        
            if (_serializableProperties.Any(o => o.Key == type))
            
                properties = properties.Where(p => IsSerializable(type, p.PropertyName)).ToList();
            

        

        return properties;
    

    private bool IsSerializable(Type type, string jsonPropertyName)
    
        if (!_serializableProperties.ContainsKey(type))
            return false;

        return _serializableProperties[type].Contains(jsonPropertyName);
    

 

自定义转换器

public class GeographyJsonConverter : JsonConverter

    private readonly Type[] _types;

    public GeographyJsonConverter ()
    
        _types = new Type[]  typeof(Geography) ;
    

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        if (value != null && typeof(Geography) == value.GetType())
        
            Geography obj = (Geography)value;
            JToken t = JToken.FromObject(obj.Country);
            t.WriteTo(writer);
        
        else
        
            JToken t = JToken.FromObject(value);
            t.WriteTo(writer);
        
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
    

    public override bool CanRead
    
        get  return false; 
    

    public override bool CanConvert(Type objectType)
    
        return _types.Any(t => t == objectType);
    

上述自定义设置的使用

   private void btnTestCode_Click(object sender, EventArgs e)
    
        Organization org = new Organization 
        
            Name = "Finance",
            ID= 1,                
            Location = new Geography() GeoID = 200, Country = "India", City = "Pune", State ="MH" 
        ;

        var changedProps = "Name,Operation";

        var dynamicContractResolver = new DynamicContractResolver();
        dynamicContractResolver.SerializableProperty(typeof(Organization), changedProps.Split(',').ToArray());

        var selected = new JsonSerializerSettings
        
            ContractResolver = dynamicContractResolver,
            Converters =  new GeographyJsonConverter() 
        ;

     
        string json = JsonConvert.SerializeObject(org, selected);

        Console.WriteLine(json);
    

【讨论】:

以上是关于如何在 json 序列化时使用 DefaultContractResolver 覆盖具有字符串值的复杂类型属性的主要内容,如果未能解决你的问题,请参考以下文章

如何在 json 序列化时使用 DefaultContractResolver 覆盖具有字符串值的复杂类型属性

在反序列化时如何忽略JSON对象数组中的空白数组?

使用 Json.Net 转换为 XML 时如何序列化空数组

序列化特定类型时如何使 JSON.Net 序列化程序调用 ToString()?

序列化json时如何忽略JsonProperty(PropertyName = "someName")?

您如何“真正”使用 Newtonsoft.Json 序列化循环引用对象?