JsonConvert 单个分隔字符串到 List<TEnum>
JsonConvert a single delimited string to List<TEnum>
我正在尝试从分隔字符串读取和写入枚举列表。但是带注释的转换器静默失败(returns null)。
我还想在构造函数中重用转换器代码,而不必使用新定义的定界符拆分字符串并使用 LINQ-Select 枚举再次解析它们。 (重复的代码片段)。
我的输入是分隔字符串
比如
var test = "foo bar hello world"
我准备了一个带有注释的枚举。
public enum FooBar {
[EnumMember(Value = "foo")]
Foo,
[EnumMember(Value = "bar")]
Bar,
[EnumMember(Value = "hello")]
Hello,
[EnumMember(Value = "world")]
World
}
我的模型
public class FooBarModel {
// Reading here stays null / fails silently (see converter below)
[JsonConverter(typeof(DelimitedStringConverter))]
public IList<FooBar> Scope { get; set; }
public FooBarModel(string scope) {
// The following throws a JsonReaderException Unexpect Character while parsing: g. Path ''.
this.Scope = JsonConvert.DeserializeObject<IList<FooBar>>(entity.RequestedScope, new DelimitedStringConverter<FooBar>());
// I assumed this is because the statement expects a json body ([ or {) not a single field.
// so I tried parsing the JToken directly.
// (of course, not at the same time. I just put this here as an illustration)
this.Scope = JToken.Parse(scope).ToObject<IList<FooBar>>();
// If there is no way to reuse my converter for my single field I will of course use the following.
this.Scope = string.Split(' ').Select(x => Enum.Parse<FooBar>(x))
// Questions
// 1. Must I or must I not do something like the last line and can I instead reuse the converter code like in the first line somehow?
// 2. Why does the parsing with the JsonConverter annotation fail silently?
}
}
最后,我的转换器 class 看起来像这样
public class DelimitedStringConverter<TEnum> : JsonConverter<IList<TEnum>> where TEnum : struct
{
private readonly char _delimiter;
public DelimitedStringConverter() : this(' ')
{
}
public DelimitedStringConverter(char delimiter)
{
_delimiter = delimiter;
}
public override void WriteJson(JsonWriter writer, IList<TEnum> value, JsonSerializer serializer)
{
var str = value.Select(v => JsonConvert.SerializeObject(v)).ToList();
writer.WriteValue(string.Join(_delimiter, str));
}
public override IList<TEnum> ReadJson(JsonReader reader, Type objectType, IList<TEnum> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (objectType != typeof(string))
return existingValue;
var val = (string)reader.Value;
var split = val.Split(_delimiter);
var result = hasExistingValue ? existingValue : new List<TEnum>();
foreach (var t in split)
{
if (Enum.TryParse(t, out TEnum r))
{
result.Add(r);
}
}
return result;
}
}
我想不通我的错误在哪里,不得已才来这里
感谢阅读!
解决方案
我添加了一个实用程序来解析分隔字符串。
此外,我添加了一个扩展方法来重用 System.Runtime.Serialization
中的 [EnumMember]
字段注释,如果没有,它将 return 字段的定义值或枚举的实际值作为字符串字段已定义。
EnumExtensions.cs
public static string GetEnumMember(this Enum value)
{
var type = value.GetType();
var name = Enum.GetName(type, value);
if (name == null) return value.ToString();
var field = type.GetField(name);
if (field == null) return value.ToString();
if (Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)) is EnumMemberAttribute attr)
return attr.Value ?? value.ToString();
return value.ToString();
}
EnumUtil.cs
public static IEnumerable<TEnum> FromJoinedString<TEnum>(string input, char delimiter = ' ') where TEnum : struct, Enum
{
var all = Enum.GetValues<TEnum>().ToArray();
var split = input.Split(delimiter);
foreach (var s in split)
foreach (var t in all)
if (t.GetEnumMember().Equals(s))
yield return t;
}
public static string ToJoinedString<TEnum>(IEnumerable<TEnum> list, char delimiter = ' ') where TEnum : struct, Enum
{
return string.Join(delimiter, list.Select(x => x.GetEnumMember()));
}
然后我在我的模型构造器中使用了这些,DelimitedStringConverter
FooBarModel.cs
public class FooBarModel {
[JsonConverter(typeof(DelimitedStringConverter))]
public IEnumerable<FooBar> Scope { get; set; }
public FooBarModel(string scope)
this.Scope = EnumUtil.FromJoinedString<FooBar>(scope);
}
}
JsonConverter
DelimitedStringConverter.cs
public class DelimitedStringConverter<TEnum> : JsonConverter<IEnumerable<TEnum>> where TEnum : struct, Enum
{
private readonly char _delimiter;
public DelimitedStringConverter() : this(' ')
{
}
public DelimitedStringConverter(char delimiter)
{
_delimiter = delimiter;
}
public override void WriteJson(JsonWriter writer, IEnumerable<TEnum> value, JsonSerializer serializer)
{
writer.WriteValue(EnumUtil.ToJoinedString(value, _delimiter));
}
public override IEnumerable<TEnum> ReadJson(JsonReader reader, Type objectType, IEnumerable<TEnum> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value.GetType() != typeof(string))
return Enumerable.Empty<TEnum>();
return EnumUtil.FromJoinedString<TEnum>((string)reader.Value, _delimiter);
}
}
这样我在逻辑中就没有我的实际枚举(这里是 FooBar)的痕迹,并且可以在未来的任务中重用这些,甚至用不同的分隔符注释它也是可能:
[JsonConverter(typeof(DelimitedStringConverter), ';')]
public IEnumerable<FooBar> Scope { get; set; }
我正在尝试从分隔字符串读取和写入枚举列表。但是带注释的转换器静默失败(returns null)。
我还想在构造函数中重用转换器代码,而不必使用新定义的定界符拆分字符串并使用 LINQ-Select 枚举再次解析它们。 (重复的代码片段)。
我的输入是分隔字符串 比如
var test = "foo bar hello world"
我准备了一个带有注释的枚举。
public enum FooBar {
[EnumMember(Value = "foo")]
Foo,
[EnumMember(Value = "bar")]
Bar,
[EnumMember(Value = "hello")]
Hello,
[EnumMember(Value = "world")]
World
}
我的模型
public class FooBarModel {
// Reading here stays null / fails silently (see converter below)
[JsonConverter(typeof(DelimitedStringConverter))]
public IList<FooBar> Scope { get; set; }
public FooBarModel(string scope) {
// The following throws a JsonReaderException Unexpect Character while parsing: g. Path ''.
this.Scope = JsonConvert.DeserializeObject<IList<FooBar>>(entity.RequestedScope, new DelimitedStringConverter<FooBar>());
// I assumed this is because the statement expects a json body ([ or {) not a single field.
// so I tried parsing the JToken directly.
// (of course, not at the same time. I just put this here as an illustration)
this.Scope = JToken.Parse(scope).ToObject<IList<FooBar>>();
// If there is no way to reuse my converter for my single field I will of course use the following.
this.Scope = string.Split(' ').Select(x => Enum.Parse<FooBar>(x))
// Questions
// 1. Must I or must I not do something like the last line and can I instead reuse the converter code like in the first line somehow?
// 2. Why does the parsing with the JsonConverter annotation fail silently?
}
}
最后,我的转换器 class 看起来像这样
public class DelimitedStringConverter<TEnum> : JsonConverter<IList<TEnum>> where TEnum : struct
{
private readonly char _delimiter;
public DelimitedStringConverter() : this(' ')
{
}
public DelimitedStringConverter(char delimiter)
{
_delimiter = delimiter;
}
public override void WriteJson(JsonWriter writer, IList<TEnum> value, JsonSerializer serializer)
{
var str = value.Select(v => JsonConvert.SerializeObject(v)).ToList();
writer.WriteValue(string.Join(_delimiter, str));
}
public override IList<TEnum> ReadJson(JsonReader reader, Type objectType, IList<TEnum> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (objectType != typeof(string))
return existingValue;
var val = (string)reader.Value;
var split = val.Split(_delimiter);
var result = hasExistingValue ? existingValue : new List<TEnum>();
foreach (var t in split)
{
if (Enum.TryParse(t, out TEnum r))
{
result.Add(r);
}
}
return result;
}
}
我想不通我的错误在哪里,不得已才来这里
感谢阅读!
解决方案
我添加了一个实用程序来解析分隔字符串。
此外,我添加了一个扩展方法来重用 System.Runtime.Serialization
中的 [EnumMember]
字段注释,如果没有,它将 return 字段的定义值或枚举的实际值作为字符串字段已定义。
EnumExtensions.cs
public static string GetEnumMember(this Enum value)
{
var type = value.GetType();
var name = Enum.GetName(type, value);
if (name == null) return value.ToString();
var field = type.GetField(name);
if (field == null) return value.ToString();
if (Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)) is EnumMemberAttribute attr)
return attr.Value ?? value.ToString();
return value.ToString();
}
EnumUtil.cs
public static IEnumerable<TEnum> FromJoinedString<TEnum>(string input, char delimiter = ' ') where TEnum : struct, Enum
{
var all = Enum.GetValues<TEnum>().ToArray();
var split = input.Split(delimiter);
foreach (var s in split)
foreach (var t in all)
if (t.GetEnumMember().Equals(s))
yield return t;
}
public static string ToJoinedString<TEnum>(IEnumerable<TEnum> list, char delimiter = ' ') where TEnum : struct, Enum
{
return string.Join(delimiter, list.Select(x => x.GetEnumMember()));
}
然后我在我的模型构造器中使用了这些,DelimitedStringConverter
FooBarModel.cs
public class FooBarModel {
[JsonConverter(typeof(DelimitedStringConverter))]
public IEnumerable<FooBar> Scope { get; set; }
public FooBarModel(string scope)
this.Scope = EnumUtil.FromJoinedString<FooBar>(scope);
}
}
JsonConverter
DelimitedStringConverter.cs
public class DelimitedStringConverter<TEnum> : JsonConverter<IEnumerable<TEnum>> where TEnum : struct, Enum
{
private readonly char _delimiter;
public DelimitedStringConverter() : this(' ')
{
}
public DelimitedStringConverter(char delimiter)
{
_delimiter = delimiter;
}
public override void WriteJson(JsonWriter writer, IEnumerable<TEnum> value, JsonSerializer serializer)
{
writer.WriteValue(EnumUtil.ToJoinedString(value, _delimiter));
}
public override IEnumerable<TEnum> ReadJson(JsonReader reader, Type objectType, IEnumerable<TEnum> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value.GetType() != typeof(string))
return Enumerable.Empty<TEnum>();
return EnumUtil.FromJoinedString<TEnum>((string)reader.Value, _delimiter);
}
}
这样我在逻辑中就没有我的实际枚举(这里是 FooBar)的痕迹,并且可以在未来的任务中重用这些,甚至用不同的分隔符注释它也是可能:
[JsonConverter(typeof(DelimitedStringConverter), ';')]
public IEnumerable<FooBar> Scope { get; set; }