Newtonsoft.Json笔记 -JRaw类型【转】

有时候我们会将一段 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 }));

输出结果:

{
  "Code": 0,
  "Message": "OK",
  "Data": {"Id":1,"Name":"码农很忙","Url":"https://www.coderbusy.com"}
}

通过本文介绍的方法,你可以巧妙地将一个 JSON 字符串嵌入到另一个对象中。这种技术不仅让你更好地掌控数据结构,而且能够绕过默认的序列化规则,为你的应用程序提供更大的灵活性和可扩展性。

posted @ 2024-01-16 15:22  .Neterr  阅读(86)  评论(0编辑  收藏  举报