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; }