反序列化mimekit.MimeMessage对象

4
我遇到了一些问题,无法反序列化一个我将其序列化为JSON字符串并存储在Redis键值缓存中的MimeKit.mimeMessage。
我能够使用json.NET或Jil成功地对mimeMessage进行序列化和存储,但是当我尝试进行反序列化时,会抛出以下错误。
json.NET引发的:
"找不到用于类型MimeKit.Header的构造函数。类应该有一个默认构造函数、一个带参数的构造函数或一个标记有JsonConstructor属性的构造函数。路径'Headers[0].Offset',行1,位置22。"
我正在使用StackExchange.Redis方法stringGet来获取序列化对象,然后将RedisValue传递给Json.Net;以下是代码片段:
RedisValue result = db.StringGet(key);

MimeMessage message = new MimeMessage();

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

据我了解,一个默认构造函数总是会被创建,这让我有点难以理解。默认构造函数是否可以被指定为私有的?如果可以,为什么要这样做呢?

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

感谢您的帮助。


4
您为什么要将MimeMessage序列化和反序列化为JSON格式?MIME本质上是用于复合文档的序列化格式,所以这对我来说没有太多意义。您可以使用message.WriteTo()和MimeMessage.Load()进行序列化和反序列化。 - jstedfast
嘿@jstedfast,感谢您的回复。我没有意识到mime是序列化格式。您是否建议使用writeTo方法将消息写入字符串?正如我在问题中所说,我想将消息存储在redis键值缓存中,从我所读的内容来看,json字符串似乎是首选的方法。 - Corey
2
好的,你不会写入一个字符串,而是会写入一个。消息通常会有多种字符集,特别是来自西欧/美国以外的消息,因此不能用单一的字符集来表示它们(因此不可能将它们表示为一个字符串)。 - jstedfast
我意识到这是一个相当老的问题,但我需要将它们保存到Mongo中,为此我需要转换为字符串。 - Krishnan Venkiteswaran
1个回答

7

首先,澄清一下:

据我了解,如果一个类没有构造函数,则始终会创建默认构造函数,这让我有些难以理解,如果将默认构造函数指定为私有,那么是否可以呢?

这不是真的。当定义一个没有构造函数的类时,会自动生成一个默认的无参构造函数。如果您提供了构造函数,则不再生成此默认构造函数。换句话说,一个类可能没有默认构造函数。


考虑到这一点,MimeKit.Header 类提供了6个构造函数,没有一个接受任何参数。因此,JSON.NET 不知道如何实例化该类,这就是引发异常的原因。

由于您无法控制 MimeKit.Header 类,一种让 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类反序列化为一个JObject。然后,我们从该JObject中挑选出HeaderIdValue,以创建一个Header实例。

需要注意的是:我不太了解这个库。这可能不是初始化Header的最佳方式,而且我可能会意外地丢弃一些信息。我只进行了一些非常基本的测试。

一旦您解决了这个问题,您就会遇到另一个与属性设置器有关的问题,这些属性设置器不接受null值。

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

解决这个问题的一个方法是创建一个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,如果头部包含MimeVersion和/或ResentMessageId头部,它们仍然会被设置。我猜这可能是MimeMessage库中内置的功能,但我对该库并不完全熟悉。
因此,在使用上述类(仅在反序列化时)时,您应该这样做:
string someJsonString = "{ ... }";

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

通过一些基本测试,似乎这个工作得很好。但在实际使用中可能会出现更多问题,所以无法保证。

希望这至少能让你开始。


1
我认为你可能需要public override bool CanWrite { get { return false; } } - dbc
哇,非常感谢您在这个答案中付出了如此多的努力,特别是解释方面。获得问题解释总比仅仅得到代码片段要有益。 - Corey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接