.Net 序列化枚举为字符串

简介: 我所做的项目是需要调用业务算法的,算法中有一个入参是油品的性质,这个性质有名称、编码、类型等属性,其中类型是固定质量性质、体积性质和其他性质这三种,所以我把其作为枚举类型。问题也由此产生,默认情况下,枚举是以其整数形式进行 JSON 序列化,这就需要同研发算法的同事约定好数值的含义。但是经过协商,算法同事要求我们传递成字符串。因此,我们希望它们在一些情况下以字符串的形式进行序列化。本文将讲解实现这一目标的各种方法。

我所做的项目是需要调用业务算法的,算法中有一个入参是油品的性质,这个性质有名称、编码、类型等属性,其中类型是固定质量性质、体积性质和其他性质这三种,所以我把其作为枚举类型。问题也由此产生,默认情况下,枚举是以其整数形式进行 JSON 序列化,这就需要同研发算法的同事约定好数值的含义。但是经过协商,算法同事要求我们传递成字符串。因此,我们希望它们在一些情况下以字符串的形式进行序列化。本文将讲解实现这一目标的各种方法。

1、枚举序列化的默认行为

首先,我们有一个性质类:

     public class Property
    {
        public string Code { get; set; }
        
        public string Name { get; set; }

        public PropertyType ProType { get; set; }
    }

    public enum PropertyType
    {
        Volume,
        Weight,
        Other
    }

我们用 System.Text.Json 中的 Serialize 方法序列化一个 Property 对象:

using System.Text.Json;

var json = JsonSerializer.Serialize(new Property
{
    Code = "DEN",
    Name = "密度",
    ProType = PropertyType.Volume
});

Console.WriteLine(json);

可以看到这个对象序列化为 Json 后的字符串:

{ "Code": "DEN", "Name": "密度", "ProType": 0}

正如我们所看到的,枚举属性的值(PropertyType.Volume)被序列化为 JSON 字符串后表现为一个整数(0)。

作为序列化结果 JSON 的消费者,在没有文档的情况下,无法知道这个整数代表的业务含义。

所以,问题来了。如何将枚举属性序列为更直观的字符串形式呢?即,如何将上面示例中的对象序列化为:

{ "Code": "DEN", "Name": "密度", "ProType": "Volume" }

要把枚举序列化为字符串,.Net 标准库 System.Text.Json 和 Newtonsoft 库都为此提供了一个转换器,分别为 JsonStringEnumConverterStringEnumConverter。下面来看看它们的用法。

2、基于标注的方式

.Net 标准库和 Newtonsoft 库都提供了一个转换器特性 JsonConverterAttribute,我们可以用它来选择性地标注要序列化为字符串形式的枚举属性或类型。

标注枚举属性

我们可以在枚举属性上使用 JsonConverterAttribute 特性来标注 ProType 属性,并使用 JsonStringEnumConverter 转换器,如下:

    using System.Text.Json.Serialization;

    public class Property
    {
        public string Code { get; set; }
        
        public string Name { get; set; }

        [JsonConverter(typeof(JsonStringEnumConverter))]
        public PropertyType ProType { get; set; }
    }

此时序列化的结果为:

{ "Code": "DEN", "Name": "密度", "ProType": "Volume" }

Newtonsoft 库也是类似的,把 JsonStringEnumConverter 替换为 StringEnumConverter 即可。

标注枚举类型

JsonConverterAttribute 特性也可以应用在自定义类型上面,把它应用在 PropertyType 枚举类型上:

using System.Text.Json.Serialization;

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum PropertyType
{
    Volume,
    Weight,
    Other
}

这样,所有的 PropertyType 枚举都会序列化为字符串形式。

3、基于选项的方式

在一些不方便修改类或枚举类型定义的情况下(如调用第三方 SDK),就不能使用标注的方式将枚举序列化为字符串了。.Net 标准库和 Newtonsoft 库还提供了带有转换选项参数的序列化方法,允许我们使用行内(Inline)方式实现这一目标。

.Net 标准库示例如下:

public static string SerializeWithStringEnum(object obj)
{
    var options = new JsonSerializerOptions();
    options.Converters.Add(new JsonStringEnumConverter());
    return JsonSerializer.Serialize(obj, options);
}

而 Newtonsoft 库稍有不同:

public static string SerializeWithStringEnum(object obj)
{
    var converter = new StringEnumConverter();
    return JsonConvert.SerializeObject(obj, converter);
}

4、全局注册的方式

基于特性标注的方式让我们可以灵活地以可控的方式来操作枚举的序列化。而基于选项的方式允许我们在特定环境下按需处理对象的序列化。但是,有没有办法让所有的枚举在默认情况下被序列化为字符串呢?答案是有的。

对控制台应用程序或 .Net 类库就简单了,直接封装一个通用的序列化方法即可。这里我们主要关心如何在 ASP.NET Core 应用程序中注册全局的序列化规则。

在 ASP.NET Core Web API 应用中,需要在 DI 管道中注册枚举转换器,如下:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
    });

var app = builder.Build();

app.MapControllers();

app.Run();

在 ASP.NET Core Minimal API 中,也是需要在 DI 管道中注册枚举转换器,但使用的是 Configure 方式:

var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<JsonOptions>(options =>
{
    options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

var app = builder.Build();

app.MapGet("/", () => new Property
{
    Code = "DEN",
    Name = "密度",
    ProType = PropertyType.Volume
});

app.Run();

如上所述,我们可以通过全局注册的方式来设置应用程序在默认情况下将枚举序列化为字符串,这样可以保持类的干净,也不需要在序列化时指定明确的选项。

5、标志枚举的序列化

标志枚举(bit-mask 枚举)的默认序列化输出也是一个整数,而不是使用标志(Flag)的组合来表示。例如:

using System.Text.Json;

var proTypes = PropertyType.Other | PropertyType.Weight;
var json = JsonSerializer.Serialize(new { ProType = proTypes });

Console.WriteLine(json);

[Flags]
public enum PropertyType
{
    Volume = 0, 
    Weight = 1, 
    Other = 2
}

序列化结果为:

{ "ProType": 3 }

我们希望输出的是枚举的组合,结果却是一个数字,这对消费者来说很难理解它真正的意义,我们希望它序列化为文件的组合。使用前面的枚举字符串转换器可以解决这个问题,以基于选项的方式为例,示例代码如下:

using System.Text.Json;
using System.Text.Json.Serialization;

var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());

var proTypes = PropertyType.Other | PropertyType.Weight;
var json = JsonSerializer.Serialize(new { ProType = proTypes }, options);

Console.WriteLine(json);
{ "ProType": "Other, Weight" }

这样就得到我们想要的文本组合序列化结果了。

6、自定义枚举字符串

在将枚举序列化为字符串时,我们可能希望进行一些微调。例如,转换为 camelCase 或其它更有意义的文本形式。

序列化为 camelCase 形式

JsonStringEnumConverter 有一个接收命名策略的重载构造函数,给其传入 JsonNamingPolicy.CamelCase 参数即可将枚举序列为 camelCase 文本形式,例如:

var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
var json = JsonSerializer.Serialize(new Property
{
    Code = "DEN",
    Name = "密度",
    ProType = PropertyType.Volume
});

Console.WriteLine(json);

序列化结果为:

{ "Code": "DEN", "Name": "密度", "ProType": "volume" }
序列化为自定义文本

由于语言中的变量命名规则,枚举成员在以常规方式序列化时并不总是能传达有意义的文本。为了解决这个问题,我们可以使用 EnumMemberAttribute 特性,它是专门为此目的引入的。我们来定义一个新的枚举:

public enum PropertyType
{
    [EnumMember(Value = "体积性质")]
    volume,

    [EnumMember(Value = "质量性质")]
    Weight,
    
    [EnumMember(Value = "其他性质")]
    Other,
}

我们重新定义了一个 PropertyType 枚举,在其成员上标注 EnumMember 特性,以提供更有意义的文本表达。

Newtonsoft 库是支持的,序列化后,我们可以看到它输出了我们想要的文本。

注:虽然 Donet 标准库原生暂不支持将枚举序列化为自定义的文本,但依然可以通过自定义转换器来实现。

7、自定义转换器

当接收到的Json格式字符串与本地已有类型不统一时,需要进行自定义的反序列化过程,反之亦然,例如Json字符串中以字符串"TRUE"表示布尔类型true(不自定义,这个过程依然走的通,只是以此举例),以字符串"FALSE"表示布尔类型false时,需要自定义如下:

/// <summary>
/// 自定义布尔类型数据转换规则
/// </summary>
public class MyBoolConverter : JsonConverter{
  private const string TrueStr = "TRUE";
  private const string FalseStr = "FALSE";
  public override bool CanConvert(Type objectType) => true;

  //反序列化  
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)  {
    if (reader.ValueType == typeof(string))
    {
      if ((string)reader.Value == TrueStr)
      {
        return true;
      }
      else      {
        return false;
      }
    }
    return false;
  }

  //序列化  
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)  {
    if (value.GetType() == typeof(bool))
    {
      bool result = (bool)value;
      if (result)
      {
        writer.WriteValue(TrueStr);
      }
      else      {
        writer.WriteValue(FalseStr);
      }
    }
  }
}

然后,在需要操作的类型定义中的字段/属性中加入该特性:

private class MyClass{
  [JsonConverter(typeof(MyBoolConverter))]
  public bool MyBool;
}

此时

string jsonStr = @"{""MyBool"": ""TRUE""}";
MyClass1 myClass = JsonConvert.DeserializeObject<MyClass1>(jsonStr);
Console.WriteLine(myClass.MyBool); //True
Console.WriteLine(JsonConvert.SerializeObject(myClass)); //{"MyBool":"TRUE"}

8、总结

枚举默认是以其整数形式进行 JSON 序列化的,这要求消费者事先了解这些数字的实际含义,给代码的理解也带来了一定的困难。所以 .Net 基础库和流行的 Newtonsoft 库都提供了将枚举序列化为字符串的多种方法。根据需求,可以选择使用本文讲的基于特性标注的方式、基于选项参数的方式和全局方式。

另外,除了可以将枚举序列化为成员名字符串,还可以自定义文本的输出,如输出为 camelCase 文本形式和自定义输出的文本内容等。

相关文章
|
2月前
|
安全 API C#
C#.Net筑基-类型系统②常见类型--枚举Enum
枚举(enum)是C#中的一种值类型,用于创建一组命名的整数常量。它们基于整数类型(如int、byte等),默认为int。枚举成员可指定值,未指定则从0开始自动递增。默认值为0。枚举可以与整数类型互相转换,并可通过`[Flags]`特性表示位域,支持位操作,用于多选场景。`System.Enum`类提供了如`HasFlag`、`GetName`等方法进行枚举操作。
|
2月前
|
安全 PHP
PHP反序列化---字符串逃逸(增加/减少)
PHP反序列化---字符串逃逸(增加/减少)
|
2月前
|
XML 存储 BI
如何把一个 ABAP 类的实例,序列化成 XML 字符串试读版
如何把一个 ABAP 类的实例,序列化成 XML 字符串试读版
|
8月前
|
开发框架 .NET
.net 字符串逗号隔开去重
.net 字符串逗号隔开去重
35 0
.net 字符串逗号隔开去重
|
8月前
|
前端开发 fastjson
mvc配置fastjson序列化枚举
mvc配置fastjson序列化枚举
74 0
|
8月前
|
开发框架 安全 前端开发
一个高性能类型安全的.NET枚举实用开源库
一个高性能类型安全的.NET枚举实用开源库
42 0
|
12月前
|
API
.NET指定图片地址下载并转换Base64字符串
.NET指定图片地址下载并转换Base64字符串
115 0
|
12月前
|
JSON 算法 Go
golang中对字符串修改操作和序列化
golang中对字符串修改操作和序列化
|
JSON 数据格式
【解决方案 十七】序列化反序列化时枚举值如何显示为字符串
【解决方案 十七】序列化反序列化时枚举值如何显示为字符串
122 0
|
存储 JSON JavaScript
.NET MVC第九章、Web Api Json序列化与反序列化
.NET MVC第九章、Web Api Json序列化与反序列化
181 0
.NET MVC第九章、Web Api Json序列化与反序列化