有时候我们会将一段 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.02405.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.02405.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.02405.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.02405.com"}<br>}