通过装饰覆盖一个特定枚举的全局 Json.NET 枚举处理

Override global Json.NET enum handling for one particular enum via decoration

我有一堆枚举,我希望 Json.NET 序列化为驼峰字符串。我的 Global.asax.cs 文件中有以下内容并且运行良好:

HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter { CamelCaseText = true });

这使得它成为这样的枚举:

public enum FavoriteWebSite {
    Whosebug,
    GoogleNews
    // Etc
}

将序列化为 "Whosebug"、"googleNews" 等值

但是,我有几个枚举是按位掩码。举个简单的例子,假设一个看起来像这样:

public enum Hobbies {
    Walking = 0x01,
    Biking = 0x02,
    // Etc
}

当我序列化这个枚举的实例时会发生什么取决于它里面有什么类型的值。例如:

Hobbies set1 = Hobbies.Walking;                  // Serializes as "walking" -- bad
Hobbies set2 = Hobbies.Walking | Hobbies.Biking; // Serializes as "3"       -- good!

我想覆盖此枚举的序列化以仅序列化为 int,同时保留全局设置以完整使用驼峰式字符串。

我尝试删除全局配置,以便默认情况下将枚举序列化为整数,然后仅将 [JsonConverter(typeof(StringEnumConverter))] 添加到非位掩码枚举。但是,这会导致 PascalCased,而不是 CamelCased 序列化。在像上面这样的方法修饰中使用 StringEnumConverter 时,我没有看到任何方法来设置 CamelCaseText 属性。

所以,回顾一下,目标是:

  1. 将单值枚举序列化为 pascalCased 字符串。
  2. 将位掩码枚举序列化为整数。

谢谢!

你的主要困难似乎是你没有用 FlagsAttribute 装饰你的标志枚举,像这样:

[Flags]
public enum Hobbies
{
    Walking = 0x01,
    Biking = 0x02,
    // Etc
}

这是标志枚举的 recommended best practice

Designing Flag Enums

√ DO apply the System.FlagsAttribute to flag enums. Do not apply this attribute to simple enums.

另见 here。如果您不这样做,许多与枚举相关的 .Net 实用程序可能无法像预期的那样为标志枚举工作。

完成此操作后,StringEnumConverter 会将具有复合值的标志枚举序列化为一组逗号分隔值,而不是您当前看到的数值:

{
  "Hobbies": "walking, biking"
}

如果您不想要这个并且仍然希望在您的 JSON 中看到标志枚举的默认数值,您可以将 StringEnumConverter 子类化为仅转换非标志枚举:

public class NonFlagStringEnumConverter : StringEnumConverter
{
    public override bool CanConvert(Type objectType)
    {
        if (!base.CanConvert(objectType))
            return false;
        return !HasFlagsAttribute(objectType);
    }

    static bool HasFlagsAttribute(Type objectType) 
    { 
        return Attribute.IsDefined(Nullable.GetUnderlyingType(objectType) ?? objectType, typeof(System.FlagsAttribute));
    }
}

然后像这样使用它:

config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new NonFlagStringEnumConverter  { CamelCaseText = true });

这将导致 Json.NET 退回到任何 global default JSON converter for enums, or to numeric serialization if there is no applicable fallback. Demo fiddle #1 here

此外,如果您需要取代在更高级别应用的转换器并强制对标志枚举进行数字序列化,请使用以下命令:

public class ForceNumericFlagEnumConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        if (!(Nullable.GetUnderlyingType(objectType) ?? objectType).IsEnum)
            return false;
        return HasFlagsAttribute(objectType);
    }

    public override bool CanRead { get { return false; } }

    public override bool CanWrite { get { return false; } }

    static bool HasFlagsAttribute(Type objectType) 
    { 
        return Attribute.IsDefined(Nullable.GetUnderlyingType(objectType) ?? objectType, typeof(System.FlagsAttribute));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

演示 fiddle #2 here.

This 博客 post 很好地解释了没有内置的方法来覆盖全局 StringEnumConverter。您需要编写自己的转换器 什么都不做 ,然后在转换时 JSON.NET 将返回到该类型的默认转换器(枚举正在序列化为它的数值)。

因此,如果您有全局转换器:

config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter { CamelCaseText = true });

您可以定义这个ForceDefaultConverter转换器

public class ForceDefaultConverter : JsonConverter
{
    public override bool CanRead => false;
    public override bool CanWrite => false;

    public override bool CanConvert(Type objectType) => throw new NotImplementedException();
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

并在 属性 上使用它来覆盖您的 默认值 StringEnumConverter.

public class ExampleDto
{
    [JsonConverter(typeof(ForceDefaultConverter))]
    public TestEnum EnumValue { get; set; }
}

或者在使用此枚举类型序列化所有对象时,如果您需要此数值,则在枚举类型本身上。

[JsonConverter(typeof(ForceDefaultConverter))]
public enum TestEnum
{
    Foo = 1,
    Bar = 2
}