有时候我们会将一段 JSON 字符串存入数据库,以期在某个接口被调用时将其返回给客户端。这种返回一般不是原样返回:我们可能需要对结果包装一下,比如将数据包在 data 字段里同时提供 code 和 message 字段。
{ "code": 200, "message": "OK", "data": [] }
这很好办,只要设计一个带泛型参数的 Result 即可:
public class Result<TData> { public int Code { get; set; } public string Message { get; set; } public TData Data { get; set; } }
简单的使用方法如下(用 Newtonsoft.Json 处理 JSON 数据):
public class Article { public string Title { get; set; } public string Content { get; set; } } var json = "从数据库拿到的 JSON 字符串"; var article = JsonConvert.DeserializeObject<Article>(json); var ret = new Result<Article> { Code = 200, Data = article, Message = "OK" };
以上代码在实际使用中存在几个问题:
- 类型必须明确指定。如果存取数据的模型不一致,接口返回的数据可能会丢失字段(也许也是个优点?)。
- 不必要的性能损失。因为不会处理数据,反序列化后再序列化就显得很没有必要。
使用 Newtonsoft.Json 中的 JRaw 类型
JRaw
是 Newtonsoft.Json 库中的一种特殊类型,用于表示 JSON 中的原始(raw)数据。它允许你在 JSON 中嵌入原始的 JSON 字符串,而不进行序列化或反序列化。当你使用 JRaw 类型时,类库不会尝试解析该字符串,而是将其保留为原始的 JSON 数据。
改进后的 Result 可以直接将 Data 字段设置为 JRaw 类型:
public class Result { public int Code { get; set; } public string Message { get; set; } public JRaw Data { get; set; } }
使用方式:
var json = "{\"Id\":1,\"Name\":\"码农很忙\",\"Url\":\"https://www.coderbusy.com\"}"; var ret = new Result { Code = 0, Message = "OK", Data = new JRaw(json) }; Console.WriteLine(JsonConvert.SerializeObject(ret));
输出结果:
{ "Code": 0, "Message": "OK", "Data": {"Id":1,"Name":"码农很忙","Url":"https://www.coderbusy.com"} }
如果我使用的是 System.Text.Json 怎么办?
System.Text.Json 是由微软官方开发在 .NET Core 3.0 之后引入的库,并在后续版本的 ASP.NET CORE 中默认使用。
虽然在 System.Text.Json 中,没有与 Newtonsoft.Json 中的 JRaw 直接对应的类。但是微软在 .NET 6.0 中引入了 Utf8JsonWriter.WriteRawValue(string json, bool skipInputValidation = false)
方法,因此可以使用一个转换器来实现类似的功能:
/// <summary> /// 将字符串值的内容序列化为原始 JSON。将验证字符串是否符合 RFC 8259 标准。 /// </summary> public class RawJsonConverter : JsonConverter<string> { public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using var doc = JsonDocument.ParseValue(ref reader); return doc.RootElement.GetRawText(); } // 是否跳过输入验证,默认为 false protected virtual bool SkipInputValidation => false; public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => // skipInputValidation : true 可提高性能,但仅在确保值表示格式良好的 JSON 时使用! writer.WriteRawValue(value, skipInputValidation: SkipInputValidation); } /// <summary> /// 将字符串值的内容序列化为原始 JSON。不验证字符串是否符合 RFC 8259 标准。 /// </summary> public class UnsafeRawJsonConverter : RawJsonConverter { // 跳过输入验证,默认为 true protected override bool SkipInputValidation => true; }
与之对应的 Result 类型也要进行一些修改,Data 字段需要改为 string 类型,并设置转换器:
public class Result { public int Code { get; set; } public string Message { get; set; } [JsonConverter(typeof(UnsafeRawJsonConverter))] public string Data { get; set; } }
使用方式:
var json = "{\"Id\":1,\"Name\":\"码农很忙\",\"Url\":\"https://www.coderbusy.com\"}"; var ret = new Result { Code = 0, Message = "OK", Data = json }; Console.WriteLine(JsonSerializer.Serialize(ret, new JsonSerializerOptions { WriteIndented = true }));
输出结果:
{<br> "Code": 0,<br> "Message": "OK",<br> "Data": {"Id":1,"Name":"码农很忙","Url":"https://www.coderbusy.com"}<br>}
结束语
通过本文介绍的方法,你可以巧妙地将一个 JSON 字符串嵌入到另一个对象中。这种技术不仅让你更好地掌控数据结构,而且能够绕过默认的序列化规则,为你的应用程序提供更大的灵活性和可扩展性。
希望本文对你在 dotnet 开发中的 JSON 处理有所帮助。在实际项目中,根据具体情况选择合适的方法,并善用 dotnet 生态系统中提供的工具和技术,将有助于提升你的开发效率和代码质量。祝愿你在 dotnet 的探索之旅中取得更多成功!