You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于自定义ISerializer<T>适配System.Text.JSON的序列化边界问题

解决方案:适配ISerializer的高效JSON序列化实现

你的核心问题是System.Text.Json序列化单个值时没有明确边界,导致连续序列化的数值(如123和456)会拼接成无分隔的字节流,反序列化时无法正确拆分。用Boxed<T>会带来额外的序列化开销,下面提供两种更优方案:

方案一:长度前缀标记(推荐,通用且高效)

通过在序列化数据前写入长度前缀,反序列化时先读取长度,再精准截取对应长度的字节流进行解析。这种方式几乎无性能开销,且适用于所有类型(包括复杂对象)。

实现代码

class JsonSerializer<T> : ISerializer<T>
{
    public void Serialize(T value, IBufferWriter<byte> writer)
    {
        // 先将对象序列化到临时缓冲区,获取字节长度
        using var ms = new MemoryStream();
        using var jsonWriter = new Utf8JsonWriter(ms);
        System.Text.Json.JsonSerializer.Serialize(jsonWriter, value);
        jsonWriter.Flush();
        var serializedBytes = ms.ToArray();
        
        // 写入VarInt编码的长度前缀(比固定4字节更省空间)
        WriteVarInt(writer, serializedBytes.Length);
        // 写入序列化后的实际数据
        writer.Write(serializedBytes);
    }

    public T Deserialize(ref SequenceReader<byte> source)
    {
        // 读取长度前缀
        if (!TryReadVarInt(ref source, out int dataLength))
        {
            throw new InvalidDataException("无法读取数据长度前缀");
        }
        
        // 截取对应长度的字节序列
        if (!source.TryReadExact(dataLength, out ReadOnlySequence<byte> targetData))
        {
            throw new InvalidDataException("数据长度不足");
        }
        
        // 反序列化目标数据
        var reader = new Utf8JsonReader(targetData);
        return System.Text.Json.JsonSerializer.Deserialize<T>(ref reader)!;
    }

    // VarInt编码写入(小长度仅需1-2字节,空间效率更高)
    private void WriteVarInt(IBufferWriter<byte> writer, int value)
    {
        do
        {
            byte b = (byte)(value & 0x7F);
            value >>= 7;
            if (value != 0)
            {
                b |= 0x80;
            }
            writer.Write(new[] { b });
        } while (value != 0);
    }

    // VarInt编码读取
    private bool TryReadVarInt(ref SequenceReader<byte> source, out int result)
    {
        result = 0;
        int shift = 0;
        byte b;
        do
        {
            if (!source.TryRead(out b))
            {
                return false;
            }
            result |= (b & 0x7F) << shift;
            shift += 7;
            if (shift > 31)
            {
                return false;
            }
        } while ((b & 0x80) != 0);
        return true;
    }
}

优势

  • 通用:支持所有T类型,包括复杂对象、字符串(即使包含特殊字符)
  • 高效:VarInt编码的长度前缀空间开销极小,序列化/反序列化的性能损耗可以忽略
  • 可靠:完全避免边界混淆问题,每个序列化单元的边界清晰

方案二:分隔符标记(仅适用于简单类型)

如果你的T仅为简单类型(如int、bool、不含特殊分隔符的字符串),可以在序列化数据末尾写入分隔符(如换行符\n),反序列化时读到分隔符就停止解析。

实现代码

class JsonSerializer<T> : ISerializer<T>
{
    private const byte Delimiter = (byte)'\n';

    public void Serialize(T value, IBufferWriter<byte> writer)
    {
        var jsonWriter = new Utf8JsonWriter(writer);
        System.Text.Json.JsonSerializer.Serialize(jsonWriter, value);
        jsonWriter.Flush();
        // 写入分隔符
        writer.Write(new[] { Delimiter });
    }

    public T Deserialize(ref SequenceReader<byte> source)
    {
        // 找到分隔符位置,不提前跳过
        if (!source.TryReadTo(out ReadOnlySequence<byte> data, Delimiter, advancePastDelimiter: false))
        {
            throw new InvalidDataException("未找到数据分隔符");
        }
        
        // 反序列化目标数据
        var reader = new Utf8JsonReader(data);
        var result = System.Text.Json.JsonSerializer.Deserialize<T>(ref reader)!;
        
        // 跳过数据和分隔符
        source.Advance(data.Length + 1);
        return result;
    }
}

注意事项

  • 仅适用于T的序列化结果中不包含分隔符的场景,比如int、bool;如果是包含换行符的字符串,会导致错误截断
  • 实现简单,性能比长度前缀方案略优,但局限性大

内容的提问来源于stack exchange,提问作者Bogey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 07:10:57