反序列化 mimekit.MimeMessage 对象

Deserialising mimekit.MimeMessage Object

我在反序列化 mimeKit.mimeMessage 时遇到一些问题,我将其序列化为 JSON 字符串并存储在 redis 键值缓存中。

我能够使用 json.NET 或 Jil 成功地序列化和存储 mimeMessage,但是当我去反序列化时,抛出以下错误。

由 json.NET

抛出

Unable to find a constructor to use for type MimeKit.Header. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'Headers[0].Offset', line 1, position 22.

我正在使用 StackExchange.Redis 方法 stringGet 获取序列化对象,然后将 RedisValue 传递给 Json.Net;下面的代码片段:

RedisValue result = db.StringGet(key);

MimeMessage message = new MimeMessage();

message = JsonConvert.DeserializeObject<MimeMessage>(result);

据我所知,默认构造函数总是被创建,这让我有点难以理解,默认构造函数是否已被指定为私有,如果是,为什么?

我也检查了返回的 JSON 字符串的格式,是正确的。

感谢您的帮助。

首先,清理一下:

It is my understanding that a default constructor is always created, which makes this a little hard to get my head around, could the default constructor have been specified as private, and if so why?

事实并非如此。当您使用 no 构造函数定义 class 时,将为您生成一个默认的 no-args 构造函数。如果您提供构造函数,则不再生成此默认构造函数。换句话说,class 可能没有默认构造函数。


考虑到这一点,MimeKit.Header class 提供了 6 个构造函数,其中 none 不带参数。因此,JSON.NET不知道如何实例化这个class,这就是为什么会出现异常。

由于您无法控制 MimeKit.Header class,让 JSON.NET 知道如何构建 Header 实例的一种方法是使用自定义转换器:

public class MimeHeaderConverter : JsonConverter
{
    public override object ReadJson(
        JsonReader reader,
        Type objectType,
        object existingValue,
        JsonSerializer serializer)
    {
        JObject obj = serializer.Deserialize<JObject>(reader);

        HeaderId headerId = obj["Id"].ToObject<HeaderId>();

        Header header = new Header(headerId, obj["Value"].ToObject<string>());

        return header;
    }

    public override void WriteJson(
        JsonWriter writer,
        object value,
        JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type t)
    {
        return typeof(Header).IsAssignableFrom(t);
    }

    public override bool CanRead { get { return true; } }

    public override bool CanWrite { get { return false; } }
}

在这里,我们首先将 Header class 反序列化为 JObject。然后,我们从 JObject 中选择 HeaderIdValue 以创建一个 Header 实例。

这里要注意:我对这个库了解不多。这可能不是实例化 Header 的最佳方式,而且我可能会不小心丢弃一些信息。我只做了一些非常基本的测试。

一旦你解决了这个问题,你就会 运行 进入另一个与不接受 null 值的 属性 setter 相关的问题。

具体来说,ResentMessageIdMimeVersion 属性在 setter 中具有逻辑,如果您提供 null,则会抛出 ArgumentException。这是一个问题,因为当实例化 MimeMessage 时,这些值 可以 为空。

一个解决方法是创建一个排除这些属性的 ContractResolver

public class MimeMessageContractResolver : DefaultContractResolver
{
    private static HashSet<string> excludedMembers = new HashSet<string>
    {
        "ResentMessageId",
        "MimeVersion"
    };

    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        var baseMembers = base.GetSerializableMembers(objectType);        

        if (typeof(MimeMessage).IsAssignableFrom(objectType)) 
        {
            baseMembers.RemoveAll(m => excludedMembers.Contains(m.Name));
        }

        return baseMembers;
    }
}

这样,我们就不会意外地将 null 传递给其中一个属性的 setter。

这里还有一个注释:我注意到即使我忽略了 MimeVersionResentMessageId 如果 headers 包含一个 MimeVersion and/or ResentMessageId header。我猜这是在 MimeMessage 的某个地方烤出来的,但同样,我对这个库并不完全熟悉。

所以要使用上面的 classes(仅在反序列化时),您可以这样做:

string someJsonString = "{ ... }";

MimeMessage deserialized = JsonConvert.DeserializeObject<MimeMessage>(
    someJsonString, new JsonSerializerSettings
    { 
        ContractResolver = new MimeMessageContractResolver(),
        Converters = new JsonConverter[] 
        { 
            new MimeHeaderConverter()
        }
    });

通过一些基本测试,这似乎工作正常。您可能会 运行 在实际使用中遇到更多问题,所以不能保证。

希望这至少能让你入门。