截至撰写本文时,NumberHandling 属性仅在 .NET 5.0 和 .NET 6.0 RC 中可用,我无法使用。不幸的是,itminus 的字符串到数字转换器也不适用于我。
所以我制作了另一个解决方案来处理不同的数字类型及其可为空的变体。我试图使代码尽可能干燥。
数字和可为空的数字类型
首先,字符串到数字和字符串到可空数字转换的主要泛型类:
public delegate T FromStringFunc<T>(string str);
public delegate T ReadingFunc<T>(ref Utf8JsonReader reader);
public delegate void WritingAction<T>(Utf8JsonWriter writer, T value);
public class StringToNumberConverter<T> : JsonConverter<T> where T : struct
{
protected ISet<TypeCode> AllowedTypeCodes { get; }
protected FromStringFunc<T> FromString { get; }
protected ReadingFunc<T> ReadValue { get; }
protected WritingAction<T> WriteValue { get; }
public StringToNumberConverter(ISet<TypeCode> allowedTypeCodes, FromStringFunc<T> fromString, ReadingFunc<T> read, WritingAction<T> write)
: base()
{
AllowedTypeCodes = allowedTypeCodes;
FromString = fromString;
ReadValue = read;
WriteValue = write;
}
public override bool CanConvert(Type typeToConvert)
{
return AllowedTypeCodes.Contains(Type.GetTypeCode(typeToConvert));
}
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
var s = reader.GetString();
return FromString(s);
}
if (reader.TokenType == JsonTokenType.Number)
return ReadValue(ref reader);
using JsonDocument document = JsonDocument.ParseValue(ref reader);
throw new Exception($"unable to parse {document.RootElement} to number");
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
WriteValue(writer, value);
}
}
public class StringToNullableNumberConverter<T> : JsonConverter<T?> where T : struct
{
private readonly StringToNumberConverter<T> stringToNumber;
protected WritingAction<T> WriteValue { get; }
public StringToNullableNumberConverter(ISet<TypeCode> allowedTypeCodes, FromStringFunc<T> fromString, ReadingFunc<T> read, WritingAction<T> write)
: base()
{
stringToNumber = new StringToNumberConverter<T>(allowedTypeCodes, fromString, read, write);
WriteValue = write;
}
public override bool CanConvert(Type typeToConvert)
{
return stringToNumber.CanConvert(Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert);
}
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return null;
return stringToNumber.Read(ref reader, typeToConvert, options);
}
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
if (!value.HasValue)
writer.WriteNullValue();
else
stringToNumber.Write(writer, value.Value, options);
}
}
然后是一个简化其使用的 util 类。它拥有不可泛化的、类型精确的转换方法和设置:
static class StringToNumberUtil
{
public static readonly ISet<TypeCode> intCodes = new HashSet<TypeCode> { TypeCode.Byte, TypeCode.Int16, TypeCode.Int32 };
public static readonly ISet<TypeCode> longCodes = new HashSet<TypeCode> { TypeCode.Int64 };
public static readonly ISet<TypeCode> decimalCodes = new HashSet<TypeCode> { TypeCode.Decimal };
public static readonly ISet<TypeCode> doubleCodes = new HashSet<TypeCode> { TypeCode.Double };
public static int ParseInt(string s) => int.Parse(s, CultureInfo.InvariantCulture);
public static long ParseLong(string s) => long.Parse(s, CultureInfo.InvariantCulture);
public static decimal ParseDecimal(string s) => decimal.Parse(s, CultureInfo.InvariantCulture);
public static double ParseDouble(string s) => double.Parse(s, CultureInfo.InvariantCulture);
public static int ReadInt(ref Utf8JsonReader reader) => reader.GetInt32();
public static long ReadLong(ref Utf8JsonReader reader) => reader.GetInt64();
public static decimal ReadDecimal(ref Utf8JsonReader reader) => reader.GetDecimal();
public static double ReadDouble(ref Utf8JsonReader reader) => reader.GetDouble();
public static void WriteInt(Utf8JsonWriter writer, int value) => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture));
public static void WriteLong(Utf8JsonWriter writer, long value) => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture));
public static void WriteDecimal(Utf8JsonWriter writer, decimal value) => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture));
public static void WriteDouble(Utf8JsonWriter writer, double value) => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture));
}
最后,您可以为各个数字类型定义便利类...
public class StringToIntConverter : StringToNumberConverter<int>
{
public StringToIntConverter()
: base(StringToNumberUtil.intCodes, StringToNumberUtil.ParseInt, StringToNumberUtil.ReadInt, StringToNumberUtil.WriteInt)
{
}
}
public class StringToNullableIntConverter : StringToNullableNumberConverter<int>
{
public StringToNullableIntConverter()
: base(StringToNumberUtil.intCodes, StringToNumberUtil.ParseInt, StringToNumberUtil.ReadInt, StringToNumberUtil.WriteInt)
{
}
}
...并像这样在 JsonSerializerOptions 中注册它们:
var options = new JsonSerializerOptions {
...
};
options.Converters.Add(new StringToIntConverter());
options.Converters.Add(new StringToNullableIntConverter());
...
(如果您愿意,也可以立即注册转换器。)
options.Converters.Add(new StringToNumberConverter<int>(StringToNumberUtil.intCodes, StringToNumberUtil.ParseInt, StringToNumberUtil.ReadInt, StringToNumberUtil.WriteInt));
options.Converters.Add(new StringToNullableNumberConverter<int>(StringToNumberUtil.intCodes, StringToNumberUtil.ParseInt, StringToNumberUtil.ReadInt, StringToNumberUtil.WriteInt));
应该反序列化为枚举的数字
如果您的 JSON 包含字符串编码的数字属性,您可以添加它,其值具有可表示为枚举的预定义含义。
public class StringToIntEnumConverter<T> : JsonConverter<T> where T : struct, System.Enum
{
private StringToIntConverter stringToInt = new StringToIntConverter();
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(T);
}
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
int val = stringToInt.Read(ref reader, typeToConvert, options);
string underlyingValue = val.ToString(CultureInfo.InvariantCulture);
return (T)Enum.Parse(typeof(T), underlyingValue);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var number = Convert.ChangeType(value, Enum.GetUnderlyingType(typeof(T)), CultureInfo.InvariantCulture);
writer.WriteStringValue(number.ToString());
}
}
public class StringToNullableIntEnumConverter<T> : JsonConverter<T?> where T : struct, System.Enum
{
private StringToIntEnumConverter<T> stringToIntEnum = new StringToIntEnumConverter<T>();
public override bool CanConvert(Type typeToConvert)
{
return stringToIntEnum.CanConvert(Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert);
}
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
return null;
return stringToIntEnum.Read(ref reader, typeToConvert, options);
}
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
if (!value.HasValue)
{
writer.WriteNullValue();
return;
}
stringToIntEnum.Write(writer, value.Value, options);
}
}
JsonSerializerOptions 中的用法:
var options = new JsonSerializerOptions {
...
};
options.Converters.Add(new StringToIntEnumConverter<OrderFlags>());
options.Converters.Add(new StringToNullableIntEnumConverter<OrderFlags>());
...