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

如何用MessagePack序列化反序列化字典列表并仅保留Results字段类型信息

解决MessagePack传输SQL查询结果时类型丢失的问题

问题场景

使用MessagePack传输SQL查询结果,定义了如下响应消息类:

public class ExecuteDatabaseQueryResponse : IResponseMessage
{
    [Key(0)]
    public Guid RequestId { get; set; }

    [Key(1)]    
    public required List<Dictionary<string, object?>>? Results { get; set; }
}

功能可正常运行,但接收端的decimal、DateTime、Guid类型值会被序列化为字符串,且无法自动还原类型。尝试给Results字段添加[MessagePackFormatter(typeof(TypelessFormatter))]或[MessagePackFormatter(typeof(DynamicObjectTypeFallbackFormatter))]特性未生效,现需仅为Results字段保留类型信息。

解决方案

1. 自定义字典值类型格式化器(推荐)

核心思路是针对Dictionary<string, object?>实现自定义格式化器,在序列化时为每个值附带类型标识,反序列化时根据标识还原原始类型,精准控制仅Results字段的类型保留。

步骤1:实现自定义格式化器

using MessagePack;
using System;
using System.Collections.Generic;

public class TypedObjectDictionaryFormatter : IMessagePackFormatter<Dictionary<string, object?>>
{
    public void Serialize(ref MessagePackWriter writer, Dictionary<string, object?> value, MessagePackSerializerOptions options)
    {
        writer.WriteMapHeader(value.Count);
        foreach (var kvp in value)
        {
            writer.Write(kvp.Key);
            
            if (kvp.Value == null)
            {
                writer.WriteNil();
                continue;
            }

            // 写入类型全名作为标识
            var valueType = kvp.Value.GetType();
            writer.Write(valueType.FullName);
            // 序列化实际值
            MessagePackSerializer.Serialize(ref writer, kvp.Value, valueType, options);
        }
    }

    public Dictionary<string, object?> Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
    {
        var mapCount = reader.ReadMapHeader();
        var resultDict = new Dictionary<string, object?>(mapCount);

        for (int i = 0; i < mapCount; i++)
        {
            var key = reader.ReadString();
            
            if (reader.TryReadNil())
            {
                resultDict[key] = null;
                continue;
            }

            // 读取类型标识并获取对应Type
            var typeName = reader.ReadString();
            var targetType = Type.GetType(typeName);
            
            if (targetType == null)
            {
                // 类型无法识别时的降级处理
                resultDict[key] = reader.ReadString();
                continue;
            }

            // 反序列化回原始类型
            var value = MessagePackSerializer.Deserialize(ref reader, targetType, options);
            resultDict[key] = value;
        }

        return resultDict;
    }
}

步骤2:为Results字段绑定格式化器

修改响应消息类,给Results字段指定自定义格式化器:

public class ExecuteDatabaseQueryResponse : IResponseMessage
{
    [Key(0)]
    public Guid RequestId { get; set; }

    [Key(1)]
    [MessagePackFormatter(typeof(TypedObjectDictionaryFormatter))]
    public required List<Dictionary<string, object?>>? Results { get; set; }
}

2. 局部启用类型保留配置

若不想编写自定义格式化器,可针对Results字段单独使用带类型保留的序列化选项。例如在序列化/反序列化时,为Results字段单独配置TypelessSerializerOptions:

// 序列化时
var response = new ExecuteDatabaseQueryResponse
{
    RequestId = Guid.NewGuid(),
    Results = queryResults
};

// 全局配置(不保留类型)用于RequestId
var globalOptions = MessagePackSerializerOptions.Standard;
// 局部配置(保留类型)用于Results
var typelessOptions = TypelessSerializerOptions.Standard;

// 手动序列化Response,分别处理不同字段
using (var writer = new MessagePackWriter(new MemoryStream()))
{
    writer.WriteMapHeader(2);
    // 序列化RequestId(用全局配置)
    writer.Write(0);
    MessagePackSerializer.Serialize(ref writer, response.RequestId, globalOptions);
    // 序列化Results(用类型保留配置)
    writer.Write(1);
    MessagePackSerializer.Serialize(ref writer, response.Results, typelessOptions);
    
    writer.Flush();
    var bytes = ((MemoryStream)writer.BaseStream).ToArray();
    // 发送bytes...
}

// 反序列化时同理,分别处理字段
using (var reader = new MessagePackReader(receivedBytes))
{
    reader.ReadMapHeader();
    var response = new ExecuteDatabaseQueryResponse();
    // 读取RequestId
    reader.ReadInt32(); // 对应Key(0)
    response.RequestId = MessagePackSerializer.Deserialize<Guid>(ref reader, globalOptions);
    // 读取Results
    reader.ReadInt32(); // 对应Key(1)
    response.Results = MessagePackSerializer.Deserialize<List<Dictionary<string, object?>>>(ref reader, typelessOptions);
}

为什么之前的特性未生效

TypelessFormatter和DynamicObjectTypeFallbackFormatter是针对整个字段类型的格式化器,而你的Results字段类型是List<Dictionary<string, object?>>,这些格式化器无法深入处理字典内部的object?值类型,因此无法保留嵌套的decimal、DateTime等类型信息。自定义格式化器则直接处理字典中每个值的类型序列化,因此能解决问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 10:24:57