自定义 JsonConverter 也序列化其值减去其中一个属性

Custom JsonConvertor that also serializes it's value minus one of it's properties

我无法满足以特定方式序列化对象的要求,即对象 ID 值成为键,对象的其余部分形成值。

简化class待序列化:

[JsonConverter(typeof(FieldTypeConvertor))]
public class FieldType {
    public string Id { get; set; }
    public string Condition  { get; set; }
    public string FieldType { get; set; }
    public string Label { get; set; }
    public string Options { get; set; }
}

这是我的 JsonConvertor WriteJson 方法:

public override void WriteJson(JsonWriter writer, UmbracoFormFieldDto value, JsonSerializer serializer)
{
    var props = value.GetType().GetProperties();
    var idProp = props.FirstOrDefault(p => p.Name.Equals("id", StringComparison.OrdinalIgnoreCase));

    var key = idProp.GetValue(value, null).ToString();

    var newObj = JsonConvert.SerializeObject(value, new JsonSerializerSettings()
    { ContractResolver = new IgnorePropertiesResolver(new[] { "id" }) });

    var container = new JObject { { key, newObj } };
    container.WriteTo(writer);
}

我明白为什么我最终会遇到 Whosebug 但不知道如何避免它以生成我需要的输出如下:

"idValueFromOriginalObj": {
    "condition": "propValue",
    "fieldype": "propValue",
    "label": "propValue",
    "options": "propValue"
}

本质上,原始对象中id的值成为序列化对象中的键,原始对象的其余属性构成值。

你的问题是,在 JsonConverter.ReadJson() 中,你试图递归序列化你的 value 对象,但由于转换器直接应用于使用 [JsonConverter(typeof(TConverter))] 的类型,你是出现堆栈溢出。

有几个选项可以禁用转换器以进行递归序列化; 详细介绍了其中的一些内容。但是,由于您已经在使用自定义合同解析器 IgnorePropertiesResolver 来忽略名为 "id" 的属性,您可以增强解析器以同时忽略类型 FieldTypeConvertor 的转换器。以下应该可以解决问题:

public class IgnorePropertiesResolver : DefaultContractResolver
{
    readonly HashSet<string> propertiesToIgnore;
    readonly HashSet<Type> converterTypesToIgnore;

    public IgnorePropertiesResolver(IEnumerable<string> propertiesToIgnore, IEnumerable<Type> converterTypesToIgnore) : base() =>
        (this.propertiesToIgnore, this.converterTypesToIgnore) = 
            ((propertiesToIgnore ?? throw new ArgumentNullException()).ToHashSet(StringComparer.OrdinalIgnoreCase), 
            (converterTypesToIgnore ?? throw new ArgumentNullException()).ToHashSet());
            
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if (propertiesToIgnore.Contains(member.Name))
            property.Ignored = true;
        if (property.Converter != null && converterTypesToIgnore.Contains(property.Converter.GetType()))
            property.Converter = null;
        return property;
    }

    protected override JsonContract CreateContract(Type objectType)
    {
        var contract = base.CreateContract(objectType);
        if (contract.Converter != null && converterTypesToIgnore.Contains(contract.Converter.GetType()))
            contract.Converter = null;
        return contract;
    }
};

然后修改FieldTypeConvertor如下:

public sealed class FieldTypeConvertor : JsonConverter<UmbracoFormFieldDto>
{
    static readonly IContractResolver innerResolver = new IgnorePropertiesResolver(new [] { "id" }, new [] { typeof(FieldTypeConvertor) })
    {
        NamingStrategy = new CamelCaseNamingStrategy(),
    };

    public override void WriteJson(JsonWriter writer, UmbracoFormFieldDto value, JsonSerializer serializer)
    {
        var props = value.GetType().GetProperties();
        var idProp = props.FirstOrDefault(p => p.Name.Equals("id", StringComparison.OrdinalIgnoreCase));
        var key = idProp.GetValue(value, null).ToString();

        writer.WriteStartObject();
        writer.WritePropertyName(key);
        JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = innerResolver }).Serialize(writer, value);
        writer.WriteEndObject();
    }

    public override UmbracoFormFieldDto ReadJson(JsonReader reader, Type objectType, UmbracoFormFieldDto existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException();
}

您的模型将按要求序列化:

{
  "idValueFromOriginalObj": {
    "condition": "propValue",
    "fieldType": "propValue",
    "label": "propValue",
    "options": "propValue"
  }
}

备注:

  • Newtonsoft 推荐您cache the contract resolver for best performance.

  • 您应该从 DefaultContractResolver 而不是 CamelCasePropertyNamesContractResolver 继承,原因在 .

  • 出于性能原因,我取消了到 JObject 的中间序列化,而是直接序列化到传入的 JsonWriter.

演示 fiddle here.