Json.NET 不能自动处理仅包含隐式运算符的值类型吗?

Jak*_*ade 6 .net c# json.net

这是我的小型自定义值类型类:

public struct PhoneNumber
{
    private readonly string _phoneNumber;

    public PhoneNumber(string phoneNumber)
    {
        _phoneNumber = phoneNumber;
        // ... lots more will happen here to validate the phone number
    }

    public static implicit operator PhoneNumber(string phoneNumber)
    {
        return new PhoneNumber(phoneNumber);
    }

    public static implicit operator string(PhoneNumber phoneNumber)
    {
        return phoneNumber.ToString();
    }

    public override string ToString()
    {
        return _phoneNumber;
    }
}
Run Code Online (Sandbox Code Playgroud)

通过与字符串之间的隐式运算符和重写的 ToString 方法,我希望 Json.NET 应该能够序列化和反序列化此类的实例。

但是,这样做:

 var pn = new PhoneNumber("1234567890");
 var json = JsonConvert.SerializeObject(pn);
Run Code Online (Sandbox Code Playgroud)

...只返回一个空的{}

我不想将属性应用于我的值类型类或实现自定义序列化器。

我可以遵循另一个约定来使 Json.NET 按照我喜欢的方式运行吗?

Vla*_*ykh 3

C# 是强类型语言。然而,人们通常在任何地方都使用string价值观。在这种情况下,语言的强类型起源没有帮助。这种问题被称为原始痴迷

.NET 提供了TypeConverter不同类型之间的值转换。Newtonsoft.Json隐式使用类型转换器。

我想出了这样的解决方案:

强类型接口

首先我们定义接口来包装原始值。

/// <summary>
/// Interface to make values strongly-typed with help of TypeConverters.
/// </summary>
/// <typeparam name="TInnerType">Inner type</typeparam>
public interface IStronglyTyped<out TInnerType>
{
    /// <summary>
    /// Inner value.
    /// </summary>
    TInnerType Value { get; }
}
Run Code Online (Sandbox Code Playgroud)

强类型类

然后我们定义实现该接口的类。

/// <summary>
/// Strongly-typed value based on inner type (e.g. <see cref="string"/> or <see cref="System.Uri"/>).
/// If you need validation then implement ".IsValid()" method.
/// </summary>
/// <typeparam name="TInnerType">Type of the inner value.</typeparam>
public abstract class StronglyTyped<TInnerType> : IStronglyTyped<TInnerType>
{
    /// <summary>
    /// Validation error format. Should contain "{0}" placeholder.
    /// </summary>
    protected virtual string ValidationErrorFormat => "'{0}' is not valid value";

    /// <summary>
    /// Inner value.
    /// </summary>
    public TInnerType Value { get; }

    /// <inheritdoc />
    protected StronglyTyped(TInnerType value)
    {
        Validate(value);

        Value = value;
    }

    private void Validate(TInnerType value)
    {
        if (!IsValid(value)) throw new StrongTypeException(GetType(), String.Format(ValidationErrorFormat, value));
    }

    /// <summary>
    /// Validates the value.
    /// </summary>
    /// <returns>'true' if value is valid.</returns>
    protected virtual bool IsValid(TInnerType value)
    {
        return true;
    }

    /// <inheritdoc />
    public override string ToString()
    {
        return Value.ToString();
    }

    /// <summary>
    /// Checks the equality of the inner values.
    /// </summary>
    protected bool Equals(StronglyTyped<TInnerType> other)
    {
        return string.Equals(Value, other.Value);
    }

    /// <inheritdoc />
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((StronglyTyped<TInnerType>)obj);
    }

    /// <inheritdoc />
    public override int GetHashCode()
    {
        return (Value != null ? Value.GetHashCode() : 0);
    }

    /// <summary>
    /// Implicit mapping to `string`.
    /// </summary>
    public static implicit operator string(StronglyTyped<TInnerType> obj)
    {
        return obj?.ToString();
    }
}
Run Code Online (Sandbox Code Playgroud)

类型转换器

有了接口和类之后,我们就实现一个通用类型转换器。

它将有助于将原始字符串值转换为强类型IStronglyTyped<TValue>

/// <summary>
/// Generic type converter for converting `string` to `TValue` (and other way around).
/// </summary>
public class StringTypeConverter<TValue> : TypeConverter
{
    /// <inheritdoc />
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string);
    }

    /// <inheritdoc />
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return destinationType == typeof(string);
    }

    /// <inheritdoc />
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value == null)
        {
            return null;
        }

        if (value is string stringValue)
        {
            return CreateInstance(stringValue);
        }

        throw new NotSupportedException($"Can't convert `{value.GetType().Name}` to `{typeof(TValue)}`");
    }

    /// <summary>
    /// Creates instance of `TValue` from string value.
    /// </summary>
    protected TValue CreateInstance(string value)
    {
        return CreateInstanceInternal(value);
    }

    /// <summary>
    /// Creates instance of `TValue` from string value.
    /// </summary>
    protected virtual TValue CreateInstanceInternal(string value)
    {
        if (typeof(IStronglyTyped<string>).IsAssignableFrom(typeof(TValue)))
        {
            return (TValue)Activator.CreateInstance(typeof(TValue), value);
        }
        else
        {
            var typeConverter = TypeDescriptor.GetConverter(typeof(TValue));
            return (TValue)typeConverter.ConvertFromString(value);
        }
    }

    /// <inheritdoc />
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            return ((TValue)value)?.ToString();
        }

        throw new NotSupportedException($"Can't convert `{typeof(TValue)}` to `{destinationType.Name}`");
    }
}
Run Code Online (Sandbox Code Playgroud)

自定义强类型类

/// <summary>
/// Phone number.
/// </summary>
[TypeConverter(typeof(StringTypeConverter<PhoneNumber>))]
public class PhoneNumber : StronglyTyped<string>
{
    /// <inheritdoc />
    public PhoneNumber(string value)
        : base(value.Trim())
    {
    }

    /// <inheritdoc />
    protected override bool IsValid(string value)
    {
        if (value.Trim() == string.Empty)
        {
            return false;
        }

        // Validation logic goes here

        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

Newtonsoft.Json该方法无需额外配置即可使用。

UPD

此外,如果您想在 Entity Framework Core 中使用这些强类型类,您需要教它如何将它们转换为string值。它不使用TypeConverter但使用ValueConverter<,>.

因此,您需要定义一个自定义值转换器。

值转换器

public class StronglyTypedValueConverter<TStronglyTyped, TInner> : ValueConverter<TStronglyTyped, string>
    where TStronglyTyped : class, IStronglyTyped<TInner>
{
    private static readonly TypeConverter TYPE_CONVERTER = TypeDescriptor.GetConverter(typeof(TStronglyTyped));

    public StronglyTypedValueConverter(ConverterMappingHints mappingHints = null)
        : base(
            stronglyTyped => FromStronglyTyped(stronglyTyped),
            value => ToStronglyTyped(value),
            mappingHints)
    {
    }

    private static string FromStronglyTyped(TStronglyTyped stronglyTyped)
    {
        var result = TYPE_CONVERTER.ConvertToString(stronglyTyped);
        return result;
    }

    private static TStronglyTyped ToStronglyTyped(object value)
    {
        var result = TYPE_CONVERTER.ConvertFrom(value);
        return result as TStronglyTyped;
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,这还不够。您必须为任何自定义类型的每个属性注册自定义转换。这要棘手得多。

  • 这是一个很好的答案 - 而且也非常详细。谢谢。:) (2认同)