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

Posted

技术标签:

【中文标题】通过装饰覆盖一个特定枚举的全局 Json.NET 枚举处理【英文标题】:Override global Json.NET enum handling for one particular enum via decoration 【发布时间】:2015-04-10 23:09:27 【问题描述】:

我有一堆枚举,我希望 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 
    ***,
    GoogleNews
    // Etc

将序列化为“***”、“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 属性。

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

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

谢谢!

【问题讨论】:

那么,您确实希望将一些枚举序列化为string,而将一些枚举序列化为int,对吗?这条评论:// Serializes as "walking" -- bad 让我觉得您希望所有这些都被序列化为 int 顺便说一下,目标 1 是为了将单值枚举序列化为驼峰式字符串,而不是 pascalCased。我说的对吗? 【参考方案1】:

您的主要困难似乎是您没有使用FlagsAttribute 装饰您的标志枚举,如下所示:

[Flags]
public enum Hobbies

    Walking = 0x01,
    Biking = 0x02,
    // Etc

这是标志枚举的recommended best practice:

设计标志枚举

√ 务必将System.FlagsAttribute 应用于标记枚举。不要将此属性应用于简单的枚举。

另见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 转换器用于枚举,或者如果没有适用的回退,则回退到数字序列化。演示小提琴 #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();
    

演示小提琴#2 here.

【讨论】:

@AFgone - 我在这里分叉了你的小提琴:dotnetfiddle.net/S52HB4,我看不出有什么问题。 UserCan ability 对象被序列化为整数5,这是此转换器所需的行为:[Flags] 枚举被序列化为整数,其他枚举为字符串。也许您有一些包含“常规”字符串枚举转换器的全局序列化程序设置?见Json.net global settings。 @AFgone - 否,如果 CanConvert(Type objectType) 从本地提供的转换器返回 false,则 Json.NET 将使用全局转换器。您可以分叉并更新小提琴以提供minimal reproducible example吗? 你是对的,我使用 JsonSerializer.Create 和设置,它工作。非常感谢您的帮助@dbc 您应该问一个单独的问题,但这似乎是 Json.NET 的一个已知错误:Failure to deserialize ulong enum #2301。 (UserCan.DoEverything的值实际上是ulong.MaxValue。) @AFgone - 添加了关于取代全局默认转换器的更新。【参考方案2】:

This 博客文章很好地解释了没有内置方法来覆盖全局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

【讨论】:

以上是关于通过装饰覆盖一个特定枚举的全局 Json.NET 枚举处理的主要内容,如果未能解决你的问题,请参考以下文章

小小装饰器

有没有办法在继承期间持久化装饰器?

无法覆盖由 TargetType 在单个特定控件上设置的全局 WPF 样式

使属性反序列化但不使用 json.net 序列化

使用 Json.Net 进行 C# 枚举反序列化:将值转换为类型时出错

你可以覆盖 AngularUI Bootstrap 中的特定模板吗?