3

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

我可以使用 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

1 回答 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第一个。然后,我们从中挑选HeaderId出来ValueJObject创建一个Header实例。

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

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

具体来说,ResentMessageIdandMimeVersion属性在 setter 中有逻辑,ArgumentException如果你提供 s 就会抛出 s null。这是一个问题,因为在实例化时这些值可能为空。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如果标题包含一个MimeVersion和/或ResentMessageId标题,它们仍然会被设置。我猜这是在MimeMessage某处被烤熟的,但同样,我对这个库并不完全熟悉。

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

string someJsonString = "{ ... }";

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

通过一些基本测试,这似乎工作正常。您在实际使用中可能会遇到更多问题,因此无法保证。

希望这至少能让你开始。

于 2015-06-08T20:03:33.537 回答