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