GFramework/docs/zh-CN/game/serialization.md
gewuyou 4b5a760643 docs(game): 收口 Game 持久化文档波次
- 重写 Game 数据、存储、序列化与设置专题页,统一为当前运行时采用路径说明

- 补充 DataRepository、SaveRepository、FileStorage、JsonSerializer 与 SettingsModel 的职责边界和最小接入路径

- 更新 documentation-full-coverage-governance 的 RP-016 恢复点与验证结果
2026-04-23 11:54:58 +08:00

163 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 序列化系统
description: 以当前 GFramework.Game.JsonSerializer 与 JsonSerializerTests 为准,说明 JSON 序列化器的配置生命周期和使用边界。
---
# 序列化系统
`GFramework.Game` 当前在序列化这一层的默认公开入口只有 `JsonSerializer`
它实现的是:
- `ISerializer`
- `IRuntimeTypeSerializer`
它不负责:
- schema 驱动配置生成
- 存档槽位管理
- 文件路径或目录布局
这些能力分别属于 source generator、repository 和 storage。
## 当前公开入口
### `JsonSerializer`
`JsonSerializer` 基于 `Newtonsoft.Json`,既支持泛型 API也支持运行时类型 API
```csharp
using GFramework.Core.Abstractions.Serializer;
using GFramework.Game.Serializer;
ISerializer serializer = new JsonSerializer();
IRuntimeTypeSerializer runtimeSerializer = new JsonSerializer();
```
当前测试覆盖的核心行为包括:
- 普通对象可正常 round-trip
- 注入的 `JsonSerializerSettings` 会直接生效
- `Settings``Converters` 暴露的是同一个活动配置实例
- 运行时类型序列化 / 反序列化可处理 `object + Type`
- 非法 JSON 会抛出带目标类型上下文的 `InvalidOperationException`
- 非法参数(例如空字符串)会保留 `ArgumentException`
- 运行时类型序列化允许 `null`,输出 `"null"`
## 配置生命周期
这部分是当前实现最容易被旧文档说错的地方。
`JsonSerializer` 不会复制你传入的 `JsonSerializerSettings`。它会直接持有并复用这份实例,以及里面的 `Converters` 集合。
这意味着推荐模式是:
1. 在组合根创建序列化器
2. 一次性完成 settings / converters 配置
3. 再把同一个实例注册给存储、repository 或 architecture
推荐写法:
```csharp
using Newtonsoft.Json;
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
settings.Converters.Add(new CoordinateConverter());
var serializer = new JsonSerializer(settings);
```
不推荐写法:
```csharp
var serializer = architecture.GetUtility<IRuntimeTypeSerializer>();
// 序列化器已经被多个组件共享后,再继续改 converter容易让并发调用看到不稳定配置。
((JsonSerializer)serializer).Converters.Add(new LateBoundConverter());
```
## 最小接入路径
### 作为底层 serializer 注册
当前更常见的采用方式不是“业务代码直接到处调 serializer”而是把它注册给存储和 repository 复用:
```csharp
using GFramework.Core.Abstractions.Serializer;
using GFramework.Game.Serializer;
var serializer = new JsonSerializer();
architecture.RegisterUtility<ISerializer>(serializer);
architecture.RegisterUtility<IRuntimeTypeSerializer>(serializer);
```
然后由:
- `FileStorage`
- `UnifiedSettingsDataRepository`
- 其他依赖 `ISerializer` / `IRuntimeTypeSerializer` 的组件
统一复用这一份实例。
### 直接处理运行时类型
当业务层拿到的是 `object + Type` 组合,而不是静态泛型类型时,再使用运行时 API
```csharp
var serializer = new JsonSerializer();
object data = new PlayerState
{
Name = "Runtime",
Level = 11
};
var json = serializer.Serialize(data, data.GetType());
var restored = serializer.Deserialize(json, data.GetType());
```
## 与存储系统的关系
`FileStorage` 已经会调用注入的 `ISerializer` 自己完成对象读写,因此当前默认接法里:
- 你可以直接 `storage.WriteAsync("profile/player", profile)`
- 不需要先手工 `serializer.Serialize(profile)` 再把字符串写回存储
手工显式调用 `Serialize(...)` 更适合这些场景:
- 需要把 JSON 发到网络或日志
- 需要和外部文本格式做中转
- 需要直接调试序列化输出内容
如果目标只是本地持久化,优先让 `IStorage` / repository 复用 serializer。
## 与配置系统的关系
不要把 `JsonSerializer``Game` 的 YAML 配置系统混在一起:
- `JsonSerializer`
- 负责运行时对象 JSON 序列化
- `Game.SourceGenerators + YamlConfigLoader`
- 负责 schema 驱动的配置表生成与 YAML 读取
如果你的目标是静态内容配置表,而不是运行时持久化对象,请改看 [`config-system.md`](./config-system.md)。
## 当前边界
- 当前公开默认实现只有 JSON没有内建 MessagePack、Binary 或 ProtoBuf 实现
- `JsonSerializer` 负责序列化,不负责对象版本迁移;版本迁移属于 `SettingsModel<TRepository>``SaveRepository<TSaveData>`
- 序列化器共享后应视为只读配置对象,避免在运行期继续修改 settings / converters
## 继续阅读
1. [存储系统](./storage.md)
2. [数据与存档系统](./data.md)
3. [配置系统](./config-system.md)
4. [Game 入口](./index.md)