Compare commits
1 Commits
feat/intro
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
6ea14b186a
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "third-party/BlazorSvgComponents"]
|
|
||||||
path = third-party/BlazorSvgComponents
|
|
||||||
url = https://git.rrricardo.top/jackfiled/BlazorSvgComponents.git
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta lang="zh-CN"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<base href="/"/>
|
<base href="/"/>
|
||||||
<link rel="stylesheet" href="@Assets["YaeBlog.styles.css"]"/>
|
<link rel="stylesheet" href="@Assets["YaeBlog.styles.css"]"/>
|
||||||
@@ -11,27 +10,19 @@
|
|||||||
<link rel="stylesheet" href="@Assets["tailwind.g.css"]"/>
|
<link rel="stylesheet" href="@Assets["tailwind.g.css"]"/>
|
||||||
<style>
|
<style>
|
||||||
@@font-face {
|
@@font-face {
|
||||||
font-family: "Font Awesome 7 Free";
|
font-family: "Font Awesome 6 Free";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
src: url(@Assets["fonts/fa-regular-400.woff2"]) format("woff2");
|
src: url(@Assets["fonts/fa-regular-400.woff2"]) format("woff2") url(@Assets["fonts/fa-regular-400.ttf"]) format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@font-face {
|
@@font-face {
|
||||||
font-family: "Font Awesome 7 Free";
|
font-family: "Font Awesome 6 Free";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
src: url(@Assets["fonts/fa-solid-900.woff2"]) format("woff2")
|
src: url(@Assets["fonts/fa-solid-900.woff2"]) format("woff2"), url(@Assets["fonts/fa-solid-900.ttf"]) format("truetype")
|
||||||
}
|
|
||||||
|
|
||||||
@@font-face {
|
|
||||||
font-family: "Font Awesome 7 Brands";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: block;
|
|
||||||
src: url(@Assets["fonts/fa-brands-400.woff2"]) format("woff2")
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<HeadOutlet/>
|
<HeadOutlet/>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-col p-3">
|
<div class="flex flex-col p-3">
|
||||||
<div class="text-3xl font-bold py-2">
|
<div class="text-3xl font-bold py-2">
|
||||||
<a href="@(Essay.EssayLink)">@(Essay.Title)</a>
|
<a href="/blog/essays/@(Essay.FileName)">@(Essay.Title)</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-2 flex flex-row justify-content-start gap-2">
|
<div class="p-2 flex flex-row justify-content-start gap-2">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="text-md">
|
<p class="text-md">
|
||||||
2021 - @(DateTimeOffset.Now.Year) ©
|
2021 - @(DateTimeOffset.Now.Year) ©
|
||||||
<Anchor Address="https://rrricardot.top" Text="初冬的朝阳"/>
|
<Anchor Address="https://rrricardot.top" Text="Ricardo Ren"/>
|
||||||
,由
|
,由
|
||||||
<Anchor Address="https://dotnet.microsoft.com" Text="@DotnetVersion"/>
|
<Anchor Address="https://dotnet.microsoft.com" Text="@DotnetVersion"/>
|
||||||
驱动。
|
驱动。
|
||||||
@@ -22,9 +22,9 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private static string DotnetVersion => $".NET {Environment.Version}";
|
private string DotnetVersion => $".NET {Environment.Version}";
|
||||||
|
|
||||||
private static string BuildCommitId => Environment.GetEnvironmentVariable("COMMIT_ID") ?? "local_build";
|
private string BuildCommitId => Environment.GetEnvironmentVariable("COMMIT_ID") ?? "local_build";
|
||||||
|
|
||||||
private static string BuildCommitUrl => $"https://git.rrricardo.top/jackfiled/YaeBlog/commit/{BuildCommitId}";
|
private string BuildCommitUrl => $"https://git.rrricardo.top/jackfiled/YaeBlog/commit/{BuildCommitId}";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
|
|
||||||
<Anchor
|
<Anchor
|
||||||
Address="/about/"
|
Address="/about/"
|
||||||
Text="关于"/>
|
Text="关于"
|
||||||
|
NewPage="@(true)"/>
|
||||||
|
|
||||||
<Anchor
|
<Anchor
|
||||||
Address="/friends"
|
Address="/friends"
|
||||||
Text="友链"/>
|
Text="友链"
|
||||||
|
NewPage="@(true)"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,45 +19,37 @@
|
|||||||
<h3 class="text-2xl">关于我</h3>
|
<h3 class="text-2xl">关于我</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mx-4">
|
<div class="py-2">
|
||||||
<div class="my-4">
|
计算机科学与技术在读大学生,明光村幼儿园附属大学所属。正处于读书和失业的叠加态。
|
||||||
<p class="my-2">
|
一般在互联网上使用<span class="italic">初冬的朝阳</span>或者<span class="italic">jackfiled</span>的名字活动。
|
||||||
正在明光村幼儿园附属研究生院攻读计算机科学与技术的硕士学位,研究AI编译器和异构编译器。
|
<span class="line-through">都是ICP备案过的人了,网名似乎没有太大的用处(</span>
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
<p class="my-2">
|
<div class="py-2">
|
||||||
一般在互联网上使用<span class="italic">初冬的朝阳</span>或者<span
|
主要是一个C#程序员,目前也在尝试写一点Rust。
|
||||||
class="italic">jackfiled</span>的名字活动。
|
总体上对于编程语言的态度是“<span>大家都是我的翅膀.jpg</span>”。
|
||||||
<span class="line-through">(都是ICP备案过的人了,网名似乎没有太大的用处)</span>
|
前后端分离的项目本当上手。
|
||||||
</p>
|
常常因为现实的压力而写一些C/C++。
|
||||||
</div>
|
<span class="line-through">对于Java和Go的评价很低。</span>
|
||||||
|
日常使用ArchLinux。
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="my-4">
|
<div class="py-2">
|
||||||
<p class="my-1">
|
100%社恐。日常生活是宅在电脑前面自言自语。
|
||||||
主要是一个C#程序员,目前也在尝试写一点Rust。
|
兴趣活动是读书和看番,目前在玩原神和三角洲。
|
||||||
<span class="line-through">
|
</div>
|
||||||
总体上对于编程语言的态度是“大家都是我的翅膀.jpg”。
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p class="my-1">
|
|
||||||
写过一些前后端分离的项目,对于RISC-V相关的开发项目也颇感兴趣。
|
|
||||||
</p>
|
|
||||||
<p class="my-1">
|
|
||||||
常常因为现实的压力而写一些C/C++,现在就在和MLIR殊死搏斗。
|
|
||||||
</p>
|
|
||||||
<p class="my-1">
|
|
||||||
日常使用Arch Linux。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-2">
|
<div class="py-4">
|
||||||
<p class="my-1">
|
常常被人批评没有梦想,这里就随便瞎编一下。
|
||||||
100%社恐。日常生活是宅在电脑前面自言自语。
|
成为嵌入式工程师,修好桌面上的<a href="https://www.bilibili.com/video/BV1VA411p7MD">HoloCubic</a>。
|
||||||
</p>
|
完成第一个不是课程设计的个人开源项目。
|
||||||
<p class="my-1">
|
遇到能够搭伙过日子的人也算是一大梦想,虽然社恐人根本不知道从何开始的说,
|
||||||
兴趣活动是读书和看番,目前在玩戴森球计划和三角洲。
|
<span class="line-through">什么时候天上才能掉美少女?</span>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="py-2">
|
||||||
|
公开的联系渠道是<a href="mailto:shicangjuner@outlook.com">电子邮件</a>。
|
||||||
|
也可以试试在各大平台搜索上面提到的名字。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -66,27 +58,22 @@
|
|||||||
<h3 class="text-2xl">关于本站</h3>
|
<h3 class="text-2xl">关于本站</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mx-4">
|
<div class="py-2">
|
||||||
<div class="my-4">
|
本站肇始于2021年下半年,在开始的两年中个人网站和博客是分别的两个网站,个人网站是裸HTML写的,博客是用
|
||||||
<p class="my-2">
|
<a href="https://hexo.io">Hexo</a>渲染的。
|
||||||
本站肇始于2021年下半年,在开始的两年中个人网站和博客是分别的两个网站,个人网站是裸HTML写的,博客是用
|
</div>
|
||||||
<Anchor Text="Hexo" Address="https://hexo.io" NewPage="@(true)"/>
|
|
||||||
的。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-4">
|
<div class="py-2">
|
||||||
<p class="my-2">
|
2024年,我们决定使用.NET技术完全重构两个网站,合二为一。虽然目前这个版本还是一个半成品,但是我们一定会努力的~(确信。
|
||||||
2024年,我们决定使用.NET技术完全重构两个网站,合二为一。虽然目前这个版本还是一个半成品,但是我们一定会努力的~(确信。
|
</div>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-4">
|
<div class="py-2">
|
||||||
<p class="my-2">
|
2025年,我们将使用的样式库从Bootstrap迁移到Tailwind CSS,将现代的前端技术同Blazor结合起来。
|
||||||
2025年,我们将使用的样式库从Bootstrap迁移到Tailwind CSS,将现代的前端技术同Blazor结合起来。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
@using YaeBlog.Abstraction
|
|
||||||
@using YaeBlog.Models
|
|
||||||
@inject IEssayContentService EssayContentInstance
|
|
||||||
|
|
||||||
<PageTitle>
|
<PageTitle>
|
||||||
Ricardo's Index
|
Ricardo's Index
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
|
|
||||||
<div class="mx-14 lg:mx-20">
|
<div class="mx-20">
|
||||||
<div class="grid grid-cols-3 py-4 lg:mx-20">
|
<div class="grid grid-cols-3 py-4">
|
||||||
<div class="col-span-3 md:col-span-1 lg:m-10">
|
<div class="col-span-3 md:col-span-1 p-5 p-lg-0">
|
||||||
<img src="@Assets["images/avatar.png"]" alt="Ricardo's Avatar"
|
<img src="@Assets["images/avatar.png"]" alt="Ricardo's Avatar" class="h-auto max-w-full">
|
||||||
class="h-auto max-w-full rounded-md border border-gray-400">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-3 md:col-span-2">
|
<div class="col-span-3 md:col-span-2">
|
||||||
<div class="flex flex-col gap-y-3 items-center md:items-start md:px-6">
|
<div class="flex flex-col px-3 gap-y-3">
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="text-3xl font-bold">初冬的朝阳</div>
|
<div class="text-3xl font-bold">初冬的朝阳 (Ricardo Ren)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
@@ -28,70 +24,36 @@
|
|||||||
<p class="text-lg italic">世界很大,时间很长。</p>
|
<p class="text-lg italic">世界很大,时间很长。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-row gap-2">
|
<div class="">
|
||||||
<a href="https://github.com/jackfiled" target="_blank">
|
<p class="text-lg">
|
||||||
<div>
|
学过一些基础的计算机知识,略懂一些代码。
|
||||||
<span class="fa-brands fa-github text-xl"></span>
|
</p>
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://git.rrricardo.top/jackfiled/" target="_blank">
|
|
||||||
<div>
|
|
||||||
<span class="gitea-icon text-xl"></span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://space.bilibili.com/378831522" target="_blank">
|
|
||||||
<div>
|
|
||||||
<span class="fa-brands fa-bilibili text-xl"></span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://xhslink.com/m/5GVDzyKf3De" target="_blank">
|
|
||||||
<div>
|
|
||||||
<span class="rednote-icon text-xl"></span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-5">
|
<div class="py-5">
|
||||||
<p class="text-lg">恕我不能亲自为您沏茶,还是非常欢迎您来,能在广阔的互联网世界中发现这里实属不易。</p>
|
<p class="text-lg">恕我不能亲自为您沏茶(?),还是非常欢迎您能够来到我的主页。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-lg pt-2">
|
<div>
|
||||||
<p class="py-1">
|
<p class="text-lg py-1">
|
||||||
正在攻读计算机科学与技术的硕士学位,研究方向是AI编译和异构编译!
|
如果您想四处看看,了解一下屏幕对面的人,可以在我的 <Anchor Address="/blog/" Text="博客"/> 看看。
|
||||||
|
如果您对于明光村幼儿园某附属技校的计算机教学感兴趣,您可以移步到
|
||||||
|
<Anchor Address="https://jackfiled.github.io/wiki/" Text="我的学习笔记"/>,
|
||||||
|
<span class="fs-5 text-decoration-line-through">虽然这笔记我自己也木有看过。</span>
|
||||||
|
如果您想批判一下我的代码,在
|
||||||
|
<Anchor Address="https://github.com/jackfiled/" Text="Github"/> 和
|
||||||
|
<Anchor Address="https://git.rrricardo.top/jackfiled/" Text="Gitea"/>
|
||||||
|
都可以找到。
|
||||||
</p>
|
</p>
|
||||||
<p class="py-1">
|
<p class="text-lg py-1">
|
||||||
喜欢优雅的代码,香甜的蛋糕等等一切可爱的事物。
|
如果您真的很闲,也可以四处搜寻一下,也许存在着一些不为人知的彩蛋。
|
||||||
</p>
|
</p>
|
||||||
<p class="py-1">
|
|
||||||
<Anchor Address="/blog/" Text="个人博客"/>中收集了我的各种奇思妙想,如果感兴趣欢迎移步。
|
|
||||||
@if (_latestEssay is not null)
|
|
||||||
{
|
|
||||||
<span>
|
|
||||||
最新的一期博客关注 <Anchor Text="@(_latestEssay.Title)" Address="@(_latestEssay.EssayLink)"/>。
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
<p class="py-1">
|
|
||||||
日常的代码开发使用自建的<Anchor Text="Gitea" Address="https://git.rrricardo.top" NewPage="@(true)"/>进行,个人
|
|
||||||
开发的各种项目都可以在上面找到。
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private BlogEssay? _latestEssay;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
base.OnInitialized();
|
|
||||||
_latestEssay = EssayContentInstance.Essays.OrderByDescending(e => e.UpdateTime).FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
.fa-brands {
|
|
||||||
font-family: "Font Awesome 7 Brands", "Font Awesome 7 Free";
|
|
||||||
font-style: normal;
|
|
||||||
font-synthesis: none;
|
|
||||||
font-variant: normal;
|
|
||||||
line-height: 1;
|
|
||||||
text-rendering: auto;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-github::before {
|
|
||||||
content: "\f09b";
|
|
||||||
color: #24292e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-bilibili::before {
|
|
||||||
content: "\e3d9";
|
|
||||||
color: #00AEEC;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gitea-icon {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: -0.125em;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
|
|
||||||
background-image: url("https://docs.gitea.com/img/gitea.svg");
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rednote-icon {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: -0.125em;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
|
|
||||||
background-image: url("images/xiaohongshu-seeklogo.svg");
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
@@ -22,8 +22,6 @@ public class BlogEssay : IComparable<BlogEssay>
|
|||||||
|
|
||||||
public required string HtmlContent { get; init; }
|
public required string HtmlContent { get; init; }
|
||||||
|
|
||||||
public string EssayLink => $"/blog/essays/{FileName}";
|
|
||||||
|
|
||||||
public BlogEssay WithNewHtmlContent(string newHtmlContent)
|
public BlogEssay WithNewHtmlContent(string newHtmlContent)
|
||||||
{
|
{
|
||||||
var essay = new BlogEssay
|
var essay = new BlogEssay
|
||||||
|
|||||||
@@ -21,6 +21,12 @@
|
|||||||
"Link": "https://ichirinko.top",
|
"Link": "https://ichirinko.top",
|
||||||
"AvatarImage": "https://ichirinko-blog-img-1.oss-cn-shenzhen.aliyuncs.com/Pic_res/img/202209122110798.png"
|
"AvatarImage": "https://ichirinko-blog-img-1.oss-cn-shenzhen.aliyuncs.com/Pic_res/img/202209122110798.png"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Name": "志田千陽",
|
||||||
|
"Description": "日出多值得",
|
||||||
|
"Link": "https://zzachary.top/",
|
||||||
|
"AvatarImage": "https://zzachary.top/img/ztqy_hub928259802d192ff5718c06370f0f2c4_48203_300x0_resize_q75_box.jpg"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Name": "不会写程序的晨旭",
|
"Name": "不会写程序的晨旭",
|
||||||
"Description": "一个普通大学生",
|
"Description": "一个普通大学生",
|
||||||
|
|||||||
344
YaeBlog/source/posts/system-text-json.md
Normal file
344
YaeBlog/source/posts/system-text-json.md
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
---
|
||||||
|
title: 使用System.Text.Json序列化和反序列化JSON
|
||||||
|
date: 2026-01-21T22:07:38.4297603+08:00
|
||||||
|
updateTime: 2026-01-21T22:07:38.4370636+08:00
|
||||||
|
tags:
|
||||||
|
- 技术笔记
|
||||||
|
- dotnet
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
如何使用`System.Text.Json`高效地序列化和反序列化JSON?
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
|
### 序列化
|
||||||
|
|
||||||
|
序列化JSON几乎总是简单的,直接使用`JsonSerializer.Serialize`就可以序列化为字符串。
|
||||||
|
|
||||||
|
唯一需要注意的是,JSON理论上唯一的数字类型`number`默认是双精度浮点数,只能**精确地**表示53位(二进制)以下的整数。在对于`long`类型进行序列化时,虽然框架可以输出正确的数值,但是JavaScript中无法正确的解析。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Fact]
|
||||||
|
public void LongSerializeTest()
|
||||||
|
{
|
||||||
|
JsonBody body = new(long.MaxValue - 1);
|
||||||
|
string output = JsonSerializer.Serialize(body);
|
||||||
|
// Output: {"Number":9223372036854775806}
|
||||||
|
outputHelper.WriteLine(output);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
上述的JSON字符串中在JavaScript中将会被解析为:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
因此在需要传递大整数的时候最好使用`String`。
|
||||||
|
|
||||||
|
### 反序列化
|
||||||
|
|
||||||
|
而反序列化中需要考虑的东西就很多了。
|
||||||
|
|
||||||
|
#### 使用记录声明反序列化的对象
|
||||||
|
|
||||||
|
在`System.Text.Json`的早期版本中,无法将JSON反序列化为`record`这类关键词声明的不可变类型,因为当时库的逻辑是首先调用类型的公共无参数构造函数构造对象,再使用setter为需要反序列化的属性赋值。在后来的版本中,序列化程序可以直接调用类型的构造函数进行反序列化,这就为反序列化到`record`和`struct`提供了方便。
|
||||||
|
|
||||||
|
例如可以使用如下的代码快速地进行反序列化:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private record JsonBody(int Code, string Username);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DeserializeTest()
|
||||||
|
{
|
||||||
|
const string input = """
|
||||||
|
{
|
||||||
|
"code": 111,
|
||||||
|
"username": "ricardo"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JsonBody? body = JsonSerializer.Deserialize<JsonBody>(input, s_serializerOptions);
|
||||||
|
Assert.NotNull(body);
|
||||||
|
|
||||||
|
Assert.Equal(111, body.Code);
|
||||||
|
Assert.Equal("ricardo", body.Username);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
但是这样进行反序列化有一个小小的坑,就是缺少对于空值的有效处理。例如对于下面的JSON,上面的代码都会正常地进行反序列化。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Fact]
|
||||||
|
public void DeserializeFromNonexistFieldTest()
|
||||||
|
{
|
||||||
|
const string input = """
|
||||||
|
{
|
||||||
|
"code": 111
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JsonBody? body = JsonSerializer.Deserialize<JsonBody>(input, s_serializerOptions);
|
||||||
|
Assert.NotNull(body);
|
||||||
|
|
||||||
|
Assert.Equal(111, body.Code);
|
||||||
|
Assert.Equal("", body.Username);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Fact]
|
||||||
|
public void DeserializeFromNullValueTest()
|
||||||
|
{
|
||||||
|
const string input = """
|
||||||
|
{
|
||||||
|
"code": 111,
|
||||||
|
"username": null
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JsonBody? body = JsonSerializer.Deserialize<JsonBody>(input, s_serializerOptions);
|
||||||
|
Assert.NotNull(body);
|
||||||
|
|
||||||
|
Assert.Equal(111, body.Code);
|
||||||
|
Assert.Equal("", body.Username);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
但是对于返回结果的校验会发现`body.Username`实际上是一个空值。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
幸好,在.NET 9中为`JsonSerializerOptions`添加了一个尊重可为空注释的选项`RespectNullableAnnotations`,将这个选项设置为`true`可以在**一定程度上**缓解这个问题。打开这个开关之后,对于`"username": null`的反序列化就会抛出异常了。
|
||||||
|
|
||||||
|
但是针对第一段JSON,也就是缺少了`username`字段的反序列化并不会报错,这就是反序列化的第二个坑,这里先按下不表。
|
||||||
|
|
||||||
|
因为在.NET运行时的设计初期并没有考虑空安全这一至关重要的特性,因此在IL中并没有针对引用类型的不可为空性的显式抽象(虽然后续的C#编译器会为所有不可为空的应用类型添加属性元数据)。所以,针对如下元素的不可为空约束是无效的:
|
||||||
|
|
||||||
|
1. 顶级类型;
|
||||||
|
2. 集合的元素类型;
|
||||||
|
3. 任何含有泛型的属性、字段和构造函数参数。
|
||||||
|
|
||||||
|
例如,针对下面这个反序列化代码并不会报错,需要程序员自行处理其中的空值:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Fact]
|
||||||
|
public void DeserializeListTest()
|
||||||
|
{
|
||||||
|
const string input = """
|
||||||
|
{
|
||||||
|
"names": [
|
||||||
|
"1",
|
||||||
|
null,
|
||||||
|
"2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JsonListBody? body = JsonSerializer.Deserialize<JsonListBody>(input, s_serializerOptions);
|
||||||
|
Assert.NotNull(body);
|
||||||
|
|
||||||
|
foreach ((int i, string value) in body.Names.Index())
|
||||||
|
{
|
||||||
|
outputHelper.WriteLine($"{i} is null? {value is null}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
运行的输出结果提示第二个元素为空:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 需要才是需要,不为空并不一定不为空
|
||||||
|
|
||||||
|
在默认的反序列化行为中,如果反序列化对象的某一个属性并不在输入的JSON对象中,反序列化器并不为报错而是直接设置为null,这显然会给破环空安全的假定,即使打开了尊重空值注释也是这样。这在.NET文档中被称为**缺失值和空值**:
|
||||||
|
|
||||||
|
- **显式空值null**将会在`RespectNullableAnnontations=true`的情况下引发异常;
|
||||||
|
- **缺少的属性**不会引发任何异常,即使对应的属性被声明为不可为空。
|
||||||
|
|
||||||
|
为了让序列化程序确保缺少属性时会报错,需要将这个属性声明为**需要的**。这一点可以通过C#的`required`关键词或者`[Required]`属性来实现。
|
||||||
|
|
||||||
|
而且,这两种属性对于C#语言和序列化程序来说是正交的,即:
|
||||||
|
|
||||||
|
1. 可以有一个可以为空的必需属性:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
MyPoco poco = new() { Value = null }; // No compiler warnings.
|
||||||
|
|
||||||
|
class MyPoco
|
||||||
|
{
|
||||||
|
public required string? Value { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 可以有一个不可为空的可选属性:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
class MyPoco
|
||||||
|
{
|
||||||
|
public string Value { get; set; } = "default";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
但是对于`record`类型来说,前者在语义上是冗余的,语法上是错误的,后者则对于程序员带来了额外的心智负担,需要手动给每一个字段加上一个额外的注解。
|
||||||
|
|
||||||
|
考虑到序列化程序也支持使用有参数的公共构造函数,上面这两个属性对于构造函数的参数来说也是成立的:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
record MyPoco(
|
||||||
|
string RequiredNonNullable,
|
||||||
|
string? RequiredNullable,
|
||||||
|
string OptionalNonNullable = "default",
|
||||||
|
string? OptionalNullable = "default"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
不过在.NET 9之前,所有构造函数的参数都被序列化程序认为是可选的。在.NET 9之后,`JsonSerializerOptions`添加了一个尊重必须构造函数参数的选项(别忘了对于`record`这类不可变对象的反序列化是通过构造函数来实现的)`RespectRequiredConstructorParameters`。在打开这个选项之后,针对缺少属性的反序列化就会正常报错了。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private static readonly JsonSerializerOptions s_serializerOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
RespectNullableAnnotations = true,
|
||||||
|
RespectRequiredConstructorParameters = true
|
||||||
|
};
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DeserializeFromNonexistFieldTest()
|
||||||
|
{
|
||||||
|
const string input = """
|
||||||
|
{
|
||||||
|
"code": 111
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<JsonBody>(input, s_serializerOptions));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 反序列化为结构
|
||||||
|
|
||||||
|
结构作为值类型,虽然在函数之间传递时需要被拷贝而带来了额外的性能开销,但是也因为这一点而可以被直接分配在栈上,给GC带来的压力较小。因此在部分需要极端性能优化的场景可以直接针对`struct`进行反序列化。
|
||||||
|
|
||||||
|
`struct`的反序列化也是通过构造函数来实现的,序列化程序遵循如下的规则来选择构造函数:
|
||||||
|
|
||||||
|
1. 对于类,如果唯一的构造函数是参数化构造函数,则选择这一构造函数;
|
||||||
|
2. 对于结构或者具有多个构造函数的类,需要使用`[JsonConstructor]`手动指定需要使用的构造函数,否则**只会**使用公共无参构造函数(如果存在)。
|
||||||
|
|
||||||
|
因此,如果需要针对不可变的结构进行反序列化,需要加上`[JsonConstructor]`注解。例如,针对下面的代码,如果不加上注解,反序列化又会静默地失败。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private struct JsonStruct
|
||||||
|
{
|
||||||
|
public int Id { get; }
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public JsonStruct(int id, string name)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DeserializeToStructTest()
|
||||||
|
{
|
||||||
|
const string input = """
|
||||||
|
{
|
||||||
|
"Id": 1,
|
||||||
|
"Name": "ricardo"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JsonStruct r = JsonSerializer.Deserialize<JsonStruct>(input, s_serializerOptions);
|
||||||
|
Assert.Equal(1, r.Id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
为了简化语法,不可变的结构可以使用`readonly record struct`语法来替代:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private readonly record struct JsonRecordStruct(int Id, string Name);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DeserializeToRecordStructTest()
|
||||||
|
{
|
||||||
|
const string input = """
|
||||||
|
{
|
||||||
|
"Id": 1,
|
||||||
|
"Name": "ricardo"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JsonRecordStruct r = JsonSerializer.Deserialize<JsonRecordStruct>(input, s_serializerOptions);
|
||||||
|
Assert.Equal(1, r.Id);
|
||||||
|
Assert.Equal("ricardo", r.Name);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
不过这里有一个很奇怪的点,使用`readonly record struct`语法之后就不需要`[JsonConstructor]`了。
|
||||||
|
|
||||||
|
可以实验一下是`readonly`还是`record`发挥了作用。
|
||||||
|
|
||||||
|
在仅仅添加了`readonly`的情况下,反序列化不会成功:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private readonly struct JsonReadonlyStruct
|
||||||
|
{
|
||||||
|
public int Id { get; }
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public JsonReadonlyStruct(int id, string name)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DeserializeToReadonlyStructTest()
|
||||||
|
{
|
||||||
|
const string input = """
|
||||||
|
{
|
||||||
|
"Id": 1,
|
||||||
|
"Name": "ricardo"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JsonReadonlyStruct r = JsonSerializer.Deserialize<JsonReadonlyStruct>(input, s_serializerOptions);
|
||||||
|
Assert.Equal(0, r.Id);
|
||||||
|
Assert.Null(r.Name);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
而在仅仅加上`record`的情况下,序列化程序就可以选择正确的构造函数了:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private record struct JsonRecordStruct(int Id, string Name);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DeserializeToRecordStructTest()
|
||||||
|
{
|
||||||
|
const string input = """
|
||||||
|
{
|
||||||
|
"Id": 1,
|
||||||
|
"Name": "ricardo"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JsonRecordStruct r = JsonSerializer.Deserialize<JsonRecordStruct>(input, s_serializerOptions);
|
||||||
|
Assert.Equal(1, r.Id);
|
||||||
|
Assert.Equal("ricardo", r.Name);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 不过这样说来`readonly record struct`中的`readonly`似乎是冗余的?
|
||||||
|
>
|
||||||
|
> 原来,`record struct`声明的对象是可变的。详见文档中对于[不可变性](https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/record#immutability)的描述。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BIN
YaeBlog/source/posts/system-text-json/image-20260120153508775.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/posts/system-text-json/image-20260120153508775.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/system-text-json/image-20260120172747047.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/posts/system-text-json/image-20260120172747047.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/system-text-json/image-20260121221219618.webp
(Stored with Git LFS)
Normal file
BIN
YaeBlog/source/posts/system-text-json/image-20260121221219618.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/wwwroot/fonts/fa-brands-400.woff2
(Stored with Git LFS)
BIN
YaeBlog/wwwroot/fonts/fa-brands-400.woff2
(Stored with Git LFS)
Binary file not shown.
BIN
YaeBlog/wwwroot/fonts/fa-regular-400.ttf
(Stored with Git LFS)
Normal file
BIN
YaeBlog/wwwroot/fonts/fa-regular-400.ttf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/wwwroot/fonts/fa-regular-400.woff2
(Stored with Git LFS)
BIN
YaeBlog/wwwroot/fonts/fa-regular-400.woff2
(Stored with Git LFS)
Binary file not shown.
BIN
YaeBlog/wwwroot/fonts/fa-solid-900.ttf
(Stored with Git LFS)
Normal file
BIN
YaeBlog/wwwroot/fonts/fa-solid-900.ttf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
YaeBlog/wwwroot/fonts/fa-solid-900.woff2
(Stored with Git LFS)
BIN
YaeBlog/wwwroot/fonts/fa-solid-900.woff2
(Stored with Git LFS)
Binary file not shown.
@@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
width="256"
|
|
||||||
height="256"
|
|
||||||
viewBox="0 0 256 256"
|
|
||||||
sodipodi:docname="XiaohongshuLOGO.png"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<defs
|
|
||||||
id="defs1" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="namedview1"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#000000"
|
|
||||||
borderopacity="0.25"
|
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1"
|
|
||||||
inkscape:zoom="2.6914062"
|
|
||||||
inkscape:cx="127.81422"
|
|
||||||
inkscape:cy="128"
|
|
||||||
inkscape:window-width="1600"
|
|
||||||
inkscape:window-height="928"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="g1" />
|
|
||||||
<g
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
inkscape:label="Image"
|
|
||||||
id="g1">
|
|
||||||
<path
|
|
||||||
style="fill:#ff2842;stroke:none"
|
|
||||||
d="M 29,0.33332825 C 13.959937,3.4666748 1.5356731,15.204498 0,31 -1.586103,47.314209 0,64.597672 0,81 v 102 c 0,18.76035 -4.7369685,44.19888 7.3333335,60 C 20.372129,260.06897 44.156731,256 63,256 h 111 35 c 5.78276,0 12.33244,0.84741 18,-0.33333 15.0401,-3.13336 27.46432,-14.87115 29,-30.66667 1.58612,-16.31419 0,-33.59769 0,-50 V 73 C 256,54.239685 260.73697,28.801102 248.66667,13 235.62787,-4.0689697 211.84329,0 193,0 H 82 47 C 41.217228,0 34.667561,-0.84741211 29,0.33332825 M 120,91 l -7,19 h 12 l -10,24 9,1 c -0.98794,2.68155 -2.31718,7.73317 -4.33334,9.83334 C 118.18945,146.3721 115.92654,146 114,146 c -4.35942,0 -13.16798,1.80539 -15.5,-3 -1.069664,-2.20416 0.465553,-4.98451 1.333336,-7 1.813624,-4.21228 4.222554,-8.51549 5.166664,-13 -2.17548,0 -4.92464,0.42967 -7,-0.33333 -7.778526,-2.85974 0.874031,-15.36435 2.66666,-19.66667 1.25875,-3.020981 2.75652,-9.584732 5.5,-11.5 C 110.01874,88.810822 115.88325,90.674988 120,91 m -79,63 c 2.750713,0 6.837379,0.81721 8.5,-2 1.769028,-2.99753 0.5,-9.58963 0.5,-13 V 106 C 50,102.90659 48.438198,93.464493 51.166668,91.5 53.41069,89.884308 62.832935,90.226166 63.833332,93 65.47065,97.539825 64,105.16241 64,110 v 32 c 0,5.48389 0.949112,11.8645 -1.333332,17 -2.177158,4.89861 -12.303417,9.27243 -17.333336,5.5 C 43.120155,162.84012 41.545292,156.59013 41,154 M 193,91 v 5 c 3.72887,0 8.4108,-0.763367 12,0.333328 11.97635,3.659424 11,15.422502 11,25.666672 1.99706,0 4.04419,-0.15562 6,0.33333 11.49335,2.87334 10,14.36401 10,23.66667 0,4.95615 0.93086,10.82184 -2.33333,15 -3.59567,4.60246 -9.48195,4 -14.66667,4 -1.6116,0 -4.26318,0.51051 -5.66667,-0.5 -2.62326,-1.88875 -3.78159,-7.50485 -4.33333,-10.5 3.28711,0 9.2179,1.12517 11.83333,-1.33334 C 219.9164,149.76859 218.65411,138.43454 215,136.5 c -1.93661,-1.02527 -4.88672,-0.5 -7,-0.5 h -15 v 29 h -14 v -29 h -14 v -14 h 14 v -12 h -9 V 96 h 9 v -5 h 14 m -32,5 v 14 h -8 v 42 h 13 v 13 H 120 L 125.33334,152.5 138,152 v -42 h -8 V 96 h 31 m 57,14 c 0,-2.84204 -0.51608,-6.25871 0.33333,-9 3.34434,-10.793121 19.61577,-2.093994 11.5,6.83333 -0.92279,1.01507 -2.54419,1.51106 -3.83333,1.83334 C 223.43948,110.30679 220.61993,110 218,110 M 41,110 36.833332,147 30,159 24,143 27,110 h 14 m 46,0 3,33 -6,15 h -2 c -5.366936,-8.49765 -6.053299,-17.26251 -7,-27 -0.672195,-6.91406 -2,-14.04004 -2,-21 h 14 m 106,0 v 12 h 9 v -12 h -9 m -75,42 -5,13 H 91 L 96.333336,151.5 104,151.66666 Z"
|
|
||||||
id="path1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.5 KiB |
1
third-party/BlazorSvgComponents
vendored
1
third-party/BlazorSvgComponents
vendored
Submodule third-party/BlazorSvgComponents deleted from 909448d9f5
Reference in New Issue
Block a user