.Net Core 3.1 Json序列号 时间、emoji格式相关问题

其实官方文档也说明了很多问题,其实多多翻翻还是能了解不少东西

微软 如何从 Newtonsoft.Json 迁移到 System.Text.Json

一、返回自定义的时间格式

直接看官方文档

按照官方文档 System.Text.Json 对于时间的支持只能保持 ISO 8601-1:2019 ,所以如果想返回此外的格式(如:yyyy-MM-dd HH:mm:ss),则需要我们自定义转换器

public class DateTimeConverter : JsonConverter<DateTime>
    public string DateTimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => DateTime.Parse(reader.GetString());
    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString(this.DateTimeFormat));

Startup.cs 使用转换器:

public void ConfigureServices(IServiceCollection services)
    services.AddControllers().AddJsonOptions(op =>
        op.JsonSerializerOptions.Converters.Add(new DateTimeConverter());

二、对于emoji和特殊文字的编码

1.情况

Core 3及以上会默认使用 System.Text.Json 来进行序列化,而他会导致一些特殊符号序列化异常

数据库:

Api返回:


2.确认原因

先通过编写一个简单的程序,看到 Newtonsoft.Json System.Text.Json 编码的区别

static void Main(string[] args)
    JsonSerializerOptions options = new JsonSerializerOptions()
        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
        WriteIndented = true
    Console.WriteLine("sжаркоt+ri中文ng            +++++-*/[^\\.]* ");
    var model = new model1 { re = "sжаркоt+ri中文ng            +++++-*/[^\\.]* \uD83D\uDC1F" };
    var sysJson = System.Text.Json.JsonSerializer.Serialize(model, options);
    var netJson = Newtonsoft.Json.JsonConvert.SerializeObject(model);
    Console.WriteLine(sysJson);
    Console.WriteLine(netJson);
    Console.ReadKey();



通过以上可以看到, System.Text.Json 对一些国家文字还算支持,但对一些特殊符号或emoji是不太支持的,他们会转移成两个 \u**** 的格式 在github的 runtime 上虽然说有人提出这个问题 #42847 #54193 但根据回复,可以看出, System.Text.Json 的设计理念是重点考虑性能、安全之类的方向。

所以这里为了其安全性,是不对多个转义符进行转义 其原因是不会扫描整个String去解析emoji这种特殊编码,而且对于转义的传参是危险的,如 XSS 之类的情景下 所以他选择将这种处理方式交给开发者

3.解决方式

1).自定义 JavaScriptEncoder

GrabYourPitchforks 留了一段可以escaping字符的代码

using System;
using System.Buffers;
using System.Text;
using System.Text.Encodings.Web;
namespace MyApp
    class Program
        static void Main(string[] args)
            MyEncoder encoder = new MyEncoder();
            string input = "Hello there!   ";
            string output = encoder.Encode(input);
            Console.WriteLine(output);
    // Any Encoder must override the four methods shown below.
    unsafe class MyEncoder : JavaScriptEncoder
        public override int MaxOutputCharactersPerInputCharacter => JavaScriptEncoder.Default.MaxOutputCharactersPerInputCharacter;
        public override unsafe int FindFirstCharacterToEncode(char* text, int textLength)
            ReadOnlySpan<char> input = new ReadOnlySpan<char>(text, textLength);
            int idx = 0;
            // Enumerate until we're out of data or saw invalid input
            while (Rune.DecodeFromUtf16(input.Slice(idx), out Rune result, out int charsConsumed) == OperationStatus.Done)
                if (WillEncode(result.Value)) { break; } // found a char that needs to be escaped
                idx += charsConsumed;
            if (idx == input.Length) { return -1; } // walked entire input without finding a char which needs escaping
            return idx;
        public override bool WillEncode(int unicodeScalar)
            // Allow U+1F603 SMILING FACE WITH OPEN MOUTH (' '),
            // and for all other chars defer to the default escaper.
            if (unicodeScalar == 0x1F603) { return false; } // does not require escaping
            else { return JavaScriptEncoder.Default.WillEncode(unicodeScalar); }
        public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten)
            // For anything that needs to be escaped, defer to the default escaper.
            return JavaScriptEncoder.Default.TryEncodeUnicodeScalar(unicodeScalar, buffer, bufferLength, out numberOfCharactersWritten);

可他这里需要重写 TextEncoder FindFirstCharacterToEncode TryEncodeUnicodeScalar ,可对于团队项目来说, unsafe 并不是能随意开关的. 在 #54193 题主也提供了自己的兼容方案,但其较为复杂,不仅使用了 unsafe 还额外引用了emoji之类的编码Map

如果能接受 unsafe , GrabYourPitchforks 的方案也是算是不错的,当然,对于我们真正需要的格式还是调整一下. 若不能接受其调整的范围,按照目前我查阅到的信息,对于Core的迁移还是直接使用 Newtonsoft.Json 来兼容旧版内容会更好

2).Core使用Newtonsoft.Json

微软 添加基于 Newtonsoft.Json 的 JSON 格式支持

在Nuget安装 Microsoft.AspNetCore.Mvc.NewtonsoftJson 因为我这边项目版本是 Core 3.1 ,所以需要使用 3.1.18 版本

Startup.cs 配置:

public void ConfigureServices(IServiceCollection services)
    services.AddControllers().AddNewtonsoftJson(options =>