Как украсить JSON.NET StringEnumConverter

Я потребляю api, который возвращает строковые значения, подобные этому. some-enum-value

Я пытаюсь поместить эти значения в перечисление, поскольку по умолчанию StringEnumConverter не выполняет работу, я пытаюсь украсить этот конвертер некоторой дополнительной логикой. Как я могу убедиться, что значения правильно десериализованы?

Следующий код - это моя попытка выполнить эту работу. Однако строка reader = new JsonTextReader(new StringReader(cleaned)); разрушает все это, поскольку base.ReadJson can not распознает строку как JSON.

Есть ли лучший способ сделать это, не выполняя всю логику excisting в StringEnumConverter? Как исправить мой подход?

public class BkStringEnumConverter : StringEnumConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.String)
        {
            var enumString = reader.Value.ToString();
            if (enumString.Contains("-"))
            {
                var cleaned = enumString.Split('-').Select(FirstToUpper).Aggregate((a, b) => a + b);
                reader = new JsonTextReader(new StringReader(cleaned));
            }
        }
        return base.ReadJson(reader, objectType, existingValue, serializer);
    }

    private static string FirstToUpper(string input)
    {
        var firstLetter = input.ToCharArray().First().ToString().ToUpper();
        return string.IsNullOrEmpty(input)
            ? input
            : firstLetter + string.Join("", input.ToCharArray().Skip(1));
    }
}

Ответы

Ответ 1

Я решил проблему, добавив атрибуты EnumMember в мои значения enum. StringEnumConverter по умолчанию в StringEnumConverter прекрасно справляется с этими атрибутами.

Пример:

public enum MyEnum
{
    [EnumMember(Value = "some-enum-value")]
    SomeEnumValue,
    Value,
    [EnumMember(Value = "some-other-value")]
    SomeOtherValue
}

Обратите внимание, что вам нужно указывать атрибуты только в случае тире или других специальных символов, которые вы не можете использовать в своем перечислении. Прописные строчные буквы обрабатываются StringEnumConverter. Поэтому, если служба возвращает значение типа someenumvalue вы должны использовать его в перечислении Someenumvalue. Если вы предпочитаете SomeEnumValue вы должны использовать атрибут EnumMember. В случае, если сервис возвращает его как этот someEnumValue вы можете просто использовать его как этот SomeEnumValue (он работает сразу после использования свойства CamelCaseText).

Вы можете легко указать ваши конвертеры и другие настройки в JsonSerializerSettings.

Вот пример настроек, которые я использую сам.

new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    Converters = new List<JsonConverter> { new StringEnumConverter { CamelCaseText = true } },
    NullValueHandling = NullValueHandling.Ignore
};

Ответ 2

Вы также можете использовать этот код:

[JsonConverter(typeof(StringEnumConverter))]
public enum ResposeStatus
{
    [EnumMember(Value = "success value")]
    Success,
    [EnumMember(Value = "fail value")]
    Fail,
    [EnumMember(Value = "error value")]
    Error
};

При сериализации JsonConvert.Serialize() будет использоваться текст внутри EnumMember.

Ответ 3

Это стало проще в Json.NET 12.0.1 с добавлением NamingStrategy к StringEnumConverter:

Новая функция - добавлена поддержка NamingStrategy в StringEnumConverter

Во-первых, поскольку в Json.NET нет встроенной стратегии именования регистра, в качестве подкласса используется SnakeCaseNamingStrategy:

public class DashCaseNamingStrategy : SnakeCaseNamingStrategy
{
    protected override string ResolvePropertyName(string name)
    {
        return base.ResolvePropertyName(name).Replace('_', '-');
    }
}

Теперь вы можете передать его в любой из нескольких конструкторов для StringEnumConverter при создании и добавлении конвертеров в JsonSerializerSettings.Converters:

var settings = new JsonSerializerSettings
{
    Converters = { new StringEnumConverter(typeof(DashCaseNamingStrategy)) },
};
var json = JsonConvert.SerializeObject(MyEnum.SomeEnumValue, settings);

Assert.IsTrue(json == "\"some-enum-value\""); // Passes successfully

MyEnum таком подходе для MyEnum аннотации вообще не требуются.

Ответ 4

Также вы можете использовать следующие методы:

public static string GetDescription(this Enum member)
        {
            if (member.GetType().IsEnum == false)
                throw new ArgumentOutOfRangeException(nameof(member), "member is not enum");

            var fieldInfo = member.GetType().GetField(member.ToString());

            if (fieldInfo == null)
                return null;

            var attributes = fieldInfo.GetCustomAttributes<DescriptionAttribute>(false).ToList();

            return attributes.Any() ? attributes.FirstOrDefault()?.Description : member.ToString();
        }

или

public static string GetDescription(this object member)
        {
            var type = member.GetType();

            var attributes = type.GetCustomAttributes<DescriptionAttribute>(false).ToList();

            return attributes.Any() ? attributes.FirstOrDefault()?.Description : member.GetType().Name;
        }

а перечисление должно иметь атрибут дескрипции. Вот так:

public enum MyEnum
    {
        [Description("some-enum-value")]
        And,
        [Description("some-enum-value")]
        Or

    }

И чем вы можете использовать свой enum следующим образом:

MyEnum.GetDescription(); //return "some-enum-value"