我可以在Json序列化器的模型的单个属性上使用TypeConverter属性吗?

时间:2018-09-12 15:16:02

标签: c# json.net typeconverter

public class JsonModel
{
    [TypeConverter(typeof(CidNumberConvertor))]
    [JsonProperty("cid_number")]
    public Cid CidNumber;

    [TypeConverter(typeof(CidHexaConvertor))]
    [JsonProperty("cid_hexa")]
    public Cid CidHexa;

    [JsonProperty("cid_default")]
    public Cid CidDefault;
}

想象一下,我有3个字段,所有字段均为Cid类型。我已经在全球注册了TypeConvertor CidHexaConvertor。似乎TypeConvertor属性在属性本身上被忽略,仅当在类/模型本身上定义时才被调用。 CidHexaConvertor具有将字符串转换为Cid并将Cid转换为字符串的方法。我以后可以共享更多代码,但是似乎无法实现这样的属性。有任何线索吗?

1 个答案:

答案 0 :(得分:1)

在Json.NET中,并非开箱即用地检查应用于成员的[TypeConverter(typeof(...))]属性。但是,您可以创建一个custom JsonConverter来包装任意TypeConverter,然后使用JsonConverterAttribute将其应用于模型。

首先,定义以下JsonConverter

public class TypeConverterJsonConverter : JsonConverter
{
    readonly TypeConverter converter;

    public TypeConverterJsonConverter(Type typeConverterType) : this((TypeConverter)Activator.CreateInstance(typeConverterType)) { }

    public TypeConverterJsonConverter(TypeConverter converter)
    {
        if (converter == null)
            throw new ArgumentNullException();
        this.converter = converter;
    }

    public override bool CanConvert(Type objectType)
    {
        return converter.CanConvertFrom(typeof(string)) && converter.CanConvertTo(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var tokenType = reader.SkipComments().TokenType;
        if (tokenType == JsonToken.Null)
            return null;
        if (!tokenType.IsPrimitive())
            throw new JsonSerializationException(string.Format("Token {0} is not primitive.", tokenType));
        var s = (string)JToken.Load(reader);
        return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var s = converter.ConvertToInvariantString(value);
        writer.WriteValue(s);
    }
}

public static partial class JsonExtensions
{
    public static JsonReader SkipComments(this JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            ;
        return reader;
    }

    public static bool IsPrimitive(this JsonToken tokenType)
    {
        switch (tokenType)
        {
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Undefined:
            case JsonToken.Null:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return true;
            default:
                return false;
        }
    }
}

然后按如下所示将其应用于您的模型:

public class JsonModel
{
    [JsonConverter(typeof(TypeConverterJsonConverter), typeof(CidNumberConvertor))]
    [TypeConverter(typeof(CidNumberConvertor))]
    [JsonProperty("cid_number")]
    public Cid CidNumber;

    [JsonConverter(typeof(TypeConverterJsonConverter), typeof(CidHexaConvertor))]
    [TypeConverter(typeof(CidHexaConvertor))]
    [JsonProperty("cid_hexa")]
    public Cid CidHexa;

    [JsonProperty("cid_default")]
    public Cid CidDefault;
}

注意:

  • JsonConverter应用TypeConverter会覆盖全局默认值Cid的使用。

  • JsonConverterAttribute(Type,Object[]) constructor用于将特定的TypeConverter类型作为参数传递给TypeConverterJsonConverter的构造函数。

  • 在生产代码中,我假设这些是属性而不是字段。

样本小提琴#1 here。 (在没有mcve的情况下,我必须创建Cid的存根实现。)

或者,如果您有许多属性要在序列化为JSON时使用已应用的TypeConverter,则可以创建一个custom ContractResolver来实例化并自动应用TypeConverterJsonConverter

public class PropertyTypeConverterContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);

        if (property.Converter == null)
        {
            // Can more than one TypeConverterAttribute be applied to a given member?  If so,
            // what should we do?
            var attr = property.AttributeProvider.GetAttributes(typeof(TypeConverterAttribute), false)
                .OfType<TypeConverterAttribute>()
                .SingleOrDefault();
            if (attr != null)
            {
                var typeConverterType = GetTypeFromName(attr.ConverterTypeName, member.DeclaringType.Assembly);
                if (typeConverterType != null)
                {
                    var jsonConverter = new TypeConverterJsonConverter(typeConverterType);
                    if (jsonConverter.CanConvert(property.PropertyType))
                    {
                        property.Converter = jsonConverter;
                        // MemberConverter is obsolete or removed in later versions of Json.NET but
                        // MUST be set identically to Converter in earlier versions.
                        property.MemberConverter = jsonConverter;
                    }
                }
            }
        }

        return property;
    }

    static Type GetTypeFromName(string typeName, Assembly declaringAssembly)
    {
        // Adapted from https://referencesource.microsoft.com/#System/compmod/system/componentmodel/PropertyDescriptor.cs,1c1ca94869d17fff
        if (string.IsNullOrEmpty(typeName))
        {
            return null;
        }

        Type typeFromGetType = Type.GetType(typeName);

        Type typeFromComponent = null;
        if (declaringAssembly != null)
        {
            if ((typeFromGetType == null) ||
                (declaringAssembly.FullName.Equals(typeFromGetType.Assembly.FullName)))
            {
                int comma = typeName.IndexOf(',');
                if (comma != -1)
                    typeName = typeName.Substring(0, comma);
                typeFromComponent = declaringAssembly.GetType(typeName);
            }
        }

        return typeFromComponent ?? typeFromGetType;
    }
}

然后按以下方式使用它:

// Cache statically for best performance.
var resolver = new PropertyTypeConverterContractResolver();
var settings = new JsonSerializerSettings
{
    ContractResolver = resolver,
};

var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);

var root2 = JsonConvert.DeserializeObject<JsonModel>(json, settings);

注意:

样本小提琴#2 here