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

_

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

> [微软 如何从 Newtonsoft.Json 迁移到 System.Text.Json](https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-core-3-1#minimal-character-escaping)

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

直接看官方文档

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

```c#

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 使用转换器:

```c#

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.JsonSystem.Text.Json编码的区别

```c#

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****的格式

在githubruntime上虽然说有人提出这个问题 #42847](https://github.com/dotnet/runtime/issues/42847) #54193](https://github.com/dotnet/runtime/issues/54193)

但根据回复,可以看出System.Text.Json的设计理念是重点考虑性能、安全之类的方向。

所以这里为了其安全性,是不对多个转义符进行转义

其原因是不会扫描整个String去解析emoji这种特殊编码,而且对于转义的传参是危险的,XSS之类的情景下

所以他选择将这种处理方式交给开发者

### 3.解决方式

#### 1).自定JavaScriptEncoder

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

<details>

<summary>折叠代码</summary>

```c#

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);

}

}

}

```

</details>

可他这里需要重TextEncoderFindFirstCharacterToEncodeTryEncodeUnicodeScalar,可对于团队项目来说unsafe并不是能随意开关的.

#54193](https://github.com/dotnet/runtime/issues/54193)题主也提供了自己的兼容方案,但其较为复杂,不仅使用unsafe还额外引用了emoji之类的编码Map

如果能接unsafeGrabYourPitchforks的方案也是算是不错的,当然,对于我们真正需要的格式还是调整一下.

若不能接受其调整的范围,按照目前我查阅到的信息,对于Core的迁移还是直接使Newtonsoft.Json来兼容旧版内容会更好

#### 2).Core使用Newtonsoft.Json

> [微软 添加基于 Newtonsoft.Json 的 JSON 格式支持](https://docs.microsoft.com/zh-cn/aspnet/core/web-api/advanced/formatting?view=aspnetcore-3.1#add-newtonsoftjson-based-json-format-support)

在Nuget安Microsoft.AspNetCore.Mvc.NewtonsoftJson

因为我这边项目版本Core 3.1,所以需要使3.1.18版本

Startup.cs 配置:

```c#

public void ConfigureServices(IServiceCollection services)

{

services.AddControllers().AddNewtonsoftJson(options =>

{

options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });

});

}

```

PostMan效果:

WPF:TreeView 实现树拖拽 2021-08-16
Typecho 博客框架部署 2021-08-20