为什么Json.NET尝试反序列化我的只读属性?

4
我一直在尝试以这样的方式对我的对象进行序列化和反序列化,使我能够指定某些属性被序列化但不被反序列化。
以下是示例代码:
public interface ISupYo
{
    string Hi { get; }
}
public class SupYo : ISupYo
{
    public string Hi { get; } = "heya";
}

public interface ISup
{
    int hiyo { get; }
}
public class Sup : ISup
{ 
    public Sup(int hiyo)
    {
        this.hiyo = hiyo;
    }

    public int hiyo { get; }
    public ISupYo yo => new SupYo();
}

var myNewSup = JsonConvert.SerializeObject(new Sup(2));
var mySup = JsonConvert.DeserializeObject<Sup>(myNewSup);

如果我从类Sup中删除构造函数,一切都很好。
但是现在反序列化失败了,并出现以下错误,因为json.net尝试构造接口ISupYo...
Newtonsoft.Json.JsonSerializationException: 'Could not create an instance of type Scratchpad.Program+ISupYo. Type is an interface or abstract class and cannot be instantiated. Path 'yo.Hi', line 1, position 21.'

我尝试了这里的指示 在Json.Net中序列化属性但不反序列化属性,但反序列化以相同方式失败。

使用JsonConverter的方式http://pmichaels.net/tag/type-is-an-interface-or-abstract-class-and-cannot-be-instantiated/是成功的,同时在序列化/反序列化期间指定typeNameHandling和format handling也是成功的。

为什么在使用/不使用默认构造函数之间会存在差异?


2
你的序列化代码是什么?具体是哪里出了问题? - DavidG
请提供一个带有错误信息的 [mcve]。 - nvoigt
@EhsanSajjad 这意味着如果我针对无参数构造函数,则无法设置自动属性Sup.hiyo。 - Gunnar Már Óttarsson
@elgonzo 不,我确实想要序列化而不是反序列化,正如我所提到的,我已经成功地使用了自定义的 contractresolver,我的主要问题是为什么只有在我使用非默认构造函数时才需要它,但在使用默认构造函数时却可以正常工作。 - Gunnar Már Óttarsson
@nvoigt已按要求更新了问题,我阅读了您提供的重复问题链接,谢谢。这是解决我最初问题的另一个好方法,但仍然没有回答我的实际问题。在您提供的问题中,提问者具有json.net支持反序列化的get/set属性。然而,json.net不支持反序列化到自动属性,请参见链接。而我的属性甚至不是自动属性,它只有一个getter,没有后备属性来保存值,在反序列化期间无法设置。 - Gunnar Már Óttarsson
显示剩余4条评论
1个回答

9
您看到的异常的原因是Json.NET功能的不幸交互:
1. 如果要反序列化的对象具有只读引用类型成员,则只要预先分配了该成员的值的内容,Json.NET将从JSON流中填充其值的内容。即使成员的声明类型是抽象的或接口,也是如此,因为返回的真实对象必须显然是具体的。 这里提供一个示例 .Net fiddle。
2. 如果要反序列化的对象指定使用参数化构造函数,则Json.NET将从JSON流中读取整个对象,将所有属性反序列化为它们声明的类型,然后按名称(模数大小写)匹配反序列化的属性以构造对象。最后,任何未匹配的属性都将设置回对象中。
3. Json.NET是单遍反序列化程序,永远不会返回重新读取先前读取的JSON令牌。
很遗憾,前两个功能不能很好地结合在一起。如果一个参数化类型的所有属性必须在构造类型之前进行反序列化,那么就无法从JSON流中填充预分配的只读成员,因为流已经被读取了。
更糟糕的是,Json.NET 似乎尝试反序列化与构造函数参数不对应但与只读成员对应的 JSON 属性,即使它应该跳过它们。由于您的 ISupYo yo 成员是一个接口,所以您会看到这个异常(除非您已经指定了 TypeNameHandling,在这种情况下不会出现异常)。这可能是一个错误;如果您愿意,您可以报告问题。具体问题似乎在于 JsonSerializerInternalReader.ResolvePropertyAndCreatorValues() 缺少检查以查看非构造函数属性是否可写 Writable

最简单的解决方法需要使用特殊的JsonConverter,因为上述的ResolvePropertyAndCreatorValues()会检查转换器的存在。首先,引入SkipDeserializationConverter

public class SkipDeserializationConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        reader.Skip();
        return existingValue;
    }

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

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

并将其应用于您的类型,如下所示:
[JsonConverter(typeof(SkipDeserializationConverter))]
public ISupYo yo { get { return new SupYo(); } }

转换器只是简单地跳过当前正在读取的令牌的所有子级,而不尝试反序列化任何内容。使用它可能比使用TypeNameHandling更可取,因为后者可能会引入安全风险,如在Newtonsoft Json中的TypeNameHandling注意事项中所解释的那样。
示例工作.Net fiddle

非常感谢,你完全解答了我的问题!看到你升级了之前的“不反序列化属性”真是太棒了 :) - Gunnar Már Óttarsson

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