通过字符串转换反序列化枚举时,如何获得空值而不是序列化错误?
How can I get a null value instead of a serialization error when deserializing an enum by string conversion?
我的一些 API 端点具有包含枚举的模型。 FluentValidation 用于验证发送的值是否满足各自的要求。
为了提高可用性和文档生成,允许将枚举作为字符串而不是整数发送。如果发送无效整数,验证发送的值是否在正确范围内工作正常,但如果发送无效字符串,序列化将失败。
public enum Foo
{
A = 1,
B = 2
}
public class Bar
{
public Foo? Foo {get;set;}
}
void Main()
{
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
var jsonString = "{\"foo\": \"C\"}";
var jsonSpan = (ReadOnlySpan<byte>)Encoding.UTF8.GetBytes(jsonString);
try
{
var result = JsonSerializer.Deserialize<Bar>(jsonSpan, options);
Console.WriteLine(result.Foo == null);
}
catch(Exception ex)
{
Console.WriteLine("Serialization Failed");
}
}
我想要的结果是当字符串与枚举的任何字段都不匹配时,简单地将枚举 属性 反序列化为 null,以便可以将模型传递给验证器以创建友好消息。
我怎样才能做到这一点?这是使用带有 System.Text.Json API.
的 net-core 3 预览版 8
你可以反序列化成一个字符串然后 TryParse
public class Bar
{
public string Foo { get; set; }
public Foo? FooEnum { get; set; }
}
...
var result = JsonSerializer.Deserialize<Bar>(jsonSpan, options);
Enum.TryParse<Foo>(result, out Bar.FooEnum);
据我所知,我有 2 个解决方案,一个使用 System.Text.Json
,另一个是 Newtonsoft
。
System.Text.Json
您使用 JsonConverter
创建自定义 class
您在 Foo 中引入了未知枚举。
而不是使用 JsonStringEnumConverter
options.Converters.Add(new JsonStringEnumConverter());
使用你自定义的classCustomEnumConverter
options.Converters.Add(new CustomEnumConverter());
所以让我们把事情放在一起:
public enum Foo
{
A = 1,
B = 2,
// what ever name and enum number that fits your logic
Unknown = 99
}
public class Bar
{
public Foo? Foo { get; set; }
}
public static void Main()
{
var options = new JsonSerializerOptions();
options.Converters.Add(new CustomEnumConverter());
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
var jsonString = "{\"foo\": \"C\"}";
var jsonSpan = (ReadOnlySpan<byte>)Encoding.UTF8.GetBytes(jsonString);
try
{
var result = JsonSerializer.Deserialize<Bar>(jsonSpan, options);
if (result.Foo == Foo.Unknown)
result.Foo = null;
Console.WriteLine(result.Foo == null);
}
catch (Exception ex)
{
Console.WriteLine("Serialization Failed" + ex.Message);
}
}
这里是代码 CustomEnumConverter
internal sealed class CustomEnumConverter : JsonConverter<Foo>
{
public override Foo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.String:
var isNullable = IsNullableType(typeToConvert);
var enumType = isNullable ? Nullable.GetUnderlyingType(typeToConvert) : typeToConvert;
var names = Enum.GetNames(enumType ?? throw new InvalidOperationException());
if (reader.TokenType != JsonTokenType.String) return Foo.Unknown;
var enumText = System.Text.Encoding.UTF8.GetString(reader.ValueSpan);
if (string.IsNullOrEmpty(enumText)) return Foo.Unknown;
var match = names.FirstOrDefault(e => string.Equals(e, enumText, StringComparison.OrdinalIgnoreCase));
return (Foo) (match != null ? Enum.Parse(enumType, match) : Foo.Unknown);
default:
throw new ArgumentOutOfRangeException();
}
}
public override void Write(Utf8JsonWriter writer, Foo value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
private static bool IsNullableType(Type t)
{
return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
}
运行 此代码应 return 正确无一例外。
对于此解决方案,我从 here 中获得了一些灵感。
另一种方法有点类似,但使用的是 Newtonsoft。
Note: Remember what I did here is just example to demonstrate stuff,
please validate every thing, test it before going production.
Newtonsoft(原答案)
使用 Newtonsoft
和自定义 JsonConverter
解决此问题的另一种方法。
你所做的是将自定义 JsonConverter
的属性添加到你的 Foo class [JsonConverter(typeof(CustomEnumConverter))]
.
如果 enum
无法识别,则将您的 class 方法设为 return null
。
你当然可以自定义几乎任何类型,并且有不同的自定义 classes.
好的,通过 Nuget 管理器安装 Newtonsoft.Json nuget 包。
我们先为您修改代码:
//add the attribute here
[JsonConverter(typeof(CustomEnumConverter))]
public enum Foo
{
A = 1,
B = 2
}
public class Bar
{
public Foo? Foo { get; set; }
}
public static void Main()
{
var jsonString = "{\"foo\": \"C\"}";
try
{
// use newtonsoft json converter
var result = JsonConvert.DeserializeObject<Bar>(jsonString);
Console.WriteLine(result.Foo == null);
}
catch (Exception ex)
{
Console.WriteLine("Serialization Failed" + ex.Message);
}
}
现在进行自定义 class:
public class CustomEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var type = IsNullableType(objectType) ? Nullable.GetUnderlyingType(objectType) : objectType;
return type != null && type.IsEnum;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var isNullable = IsNullableType(objectType);
var enumType = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType;
var names = Enum.GetNames(enumType ?? throw new InvalidOperationException());
if (reader.TokenType != JsonToken.String) return null;
var enumText = reader.Value.ToString();
if (string.IsNullOrEmpty(enumText)) return null;
var match = names.FirstOrDefault(e => string.Equals(e, enumText, StringComparison.OrdinalIgnoreCase));
return match != null ? Enum.Parse(enumType, match) : null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override bool CanWrite => true;
private static bool IsNullableType(Type t)
{
return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
}
现在是测试时间。
当我们在没有 [JsonConverter(typeof(CustomEnumConverter))]
的情况下启动程序时,我们会得到如下所示的错误:
但是当我们再次添加 [JsonConverter(typeof(CustomEnumConverter))]
和 运行 程序时,它就可以工作了:
链接:
- https://www.newtonsoft.com/json
- 我从这个答案中得到启发:
How can I ignore unknown enum values during json deserialization?
- https://bytefish.de/blog/enums_json_net/
我的一些 API 端点具有包含枚举的模型。 FluentValidation 用于验证发送的值是否满足各自的要求。
为了提高可用性和文档生成,允许将枚举作为字符串而不是整数发送。如果发送无效整数,验证发送的值是否在正确范围内工作正常,但如果发送无效字符串,序列化将失败。
public enum Foo
{
A = 1,
B = 2
}
public class Bar
{
public Foo? Foo {get;set;}
}
void Main()
{
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
var jsonString = "{\"foo\": \"C\"}";
var jsonSpan = (ReadOnlySpan<byte>)Encoding.UTF8.GetBytes(jsonString);
try
{
var result = JsonSerializer.Deserialize<Bar>(jsonSpan, options);
Console.WriteLine(result.Foo == null);
}
catch(Exception ex)
{
Console.WriteLine("Serialization Failed");
}
}
我想要的结果是当字符串与枚举的任何字段都不匹配时,简单地将枚举 属性 反序列化为 null,以便可以将模型传递给验证器以创建友好消息。
我怎样才能做到这一点?这是使用带有 System.Text.Json API.
的 net-core 3 预览版 8你可以反序列化成一个字符串然后 TryParse
public class Bar
{
public string Foo { get; set; }
public Foo? FooEnum { get; set; }
}
...
var result = JsonSerializer.Deserialize<Bar>(jsonSpan, options);
Enum.TryParse<Foo>(result, out Bar.FooEnum);
据我所知,我有 2 个解决方案,一个使用 System.Text.Json
,另一个是 Newtonsoft
。
System.Text.Json
您使用 JsonConverter
您在 Foo 中引入了未知枚举。
而不是使用 JsonStringEnumConverter
options.Converters.Add(new JsonStringEnumConverter());
使用你自定义的classCustomEnumConverter
options.Converters.Add(new CustomEnumConverter());
所以让我们把事情放在一起:
public enum Foo
{
A = 1,
B = 2,
// what ever name and enum number that fits your logic
Unknown = 99
}
public class Bar
{
public Foo? Foo { get; set; }
}
public static void Main()
{
var options = new JsonSerializerOptions();
options.Converters.Add(new CustomEnumConverter());
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
var jsonString = "{\"foo\": \"C\"}";
var jsonSpan = (ReadOnlySpan<byte>)Encoding.UTF8.GetBytes(jsonString);
try
{
var result = JsonSerializer.Deserialize<Bar>(jsonSpan, options);
if (result.Foo == Foo.Unknown)
result.Foo = null;
Console.WriteLine(result.Foo == null);
}
catch (Exception ex)
{
Console.WriteLine("Serialization Failed" + ex.Message);
}
}
这里是代码 CustomEnumConverter
internal sealed class CustomEnumConverter : JsonConverter<Foo>
{
public override Foo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.String:
var isNullable = IsNullableType(typeToConvert);
var enumType = isNullable ? Nullable.GetUnderlyingType(typeToConvert) : typeToConvert;
var names = Enum.GetNames(enumType ?? throw new InvalidOperationException());
if (reader.TokenType != JsonTokenType.String) return Foo.Unknown;
var enumText = System.Text.Encoding.UTF8.GetString(reader.ValueSpan);
if (string.IsNullOrEmpty(enumText)) return Foo.Unknown;
var match = names.FirstOrDefault(e => string.Equals(e, enumText, StringComparison.OrdinalIgnoreCase));
return (Foo) (match != null ? Enum.Parse(enumType, match) : Foo.Unknown);
default:
throw new ArgumentOutOfRangeException();
}
}
public override void Write(Utf8JsonWriter writer, Foo value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
private static bool IsNullableType(Type t)
{
return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
}
运行 此代码应 return 正确无一例外。
对于此解决方案,我从 here 中获得了一些灵感。
另一种方法有点类似,但使用的是 Newtonsoft。
Note: Remember what I did here is just example to demonstrate stuff, please validate every thing, test it before going production.
Newtonsoft(原答案)
使用 Newtonsoft
和自定义 JsonConverter
解决此问题的另一种方法。
你所做的是将自定义 JsonConverter
的属性添加到你的 Foo class [JsonConverter(typeof(CustomEnumConverter))]
.
如果 enum
无法识别,则将您的 class 方法设为 return null
。
你当然可以自定义几乎任何类型,并且有不同的自定义 classes.
好的,通过 Nuget 管理器安装 Newtonsoft.Json nuget 包。
我们先为您修改代码:
//add the attribute here
[JsonConverter(typeof(CustomEnumConverter))]
public enum Foo
{
A = 1,
B = 2
}
public class Bar
{
public Foo? Foo { get; set; }
}
public static void Main()
{
var jsonString = "{\"foo\": \"C\"}";
try
{
// use newtonsoft json converter
var result = JsonConvert.DeserializeObject<Bar>(jsonString);
Console.WriteLine(result.Foo == null);
}
catch (Exception ex)
{
Console.WriteLine("Serialization Failed" + ex.Message);
}
}
现在进行自定义 class:
public class CustomEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var type = IsNullableType(objectType) ? Nullable.GetUnderlyingType(objectType) : objectType;
return type != null && type.IsEnum;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var isNullable = IsNullableType(objectType);
var enumType = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType;
var names = Enum.GetNames(enumType ?? throw new InvalidOperationException());
if (reader.TokenType != JsonToken.String) return null;
var enumText = reader.Value.ToString();
if (string.IsNullOrEmpty(enumText)) return null;
var match = names.FirstOrDefault(e => string.Equals(e, enumText, StringComparison.OrdinalIgnoreCase));
return match != null ? Enum.Parse(enumType, match) : null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override bool CanWrite => true;
private static bool IsNullableType(Type t)
{
return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
}
现在是测试时间。
当我们在没有 [JsonConverter(typeof(CustomEnumConverter))]
的情况下启动程序时,我们会得到如下所示的错误:
但是当我们再次添加 [JsonConverter(typeof(CustomEnumConverter))]
和 运行 程序时,它就可以工作了:
链接:
- https://www.newtonsoft.com/json
- 我从这个答案中得到启发:
How can I ignore unknown enum values during json deserialization? - https://bytefish.de/blog/enums_json_net/