【问题标题】:Serialization & Deserialization wont work with Dictionary as Backing field [duplicate]序列化和反序列化不适用于字典作为支持字段[重复]
【发布时间】:2020-07-16 19:09:29
【问题描述】:

当字典隐藏在属性后面的支持字段中时,我遇到了一个奇怪的字典序列化/反序列化问题。

这是小提琴: https://dotnetfiddle.net/RFZEur

最终结果:

  • 语言:C#
  • 框架:.Net Framework 4.8
  • 有一个 私有支持字段,它与 组合成对
  • 可以序列化为字符串列表(不包括整数,指向引用的支持字段)
  • 无法从序列化列表中反序列化 - 不填充支持字典。
public class SanityChecks
{
    private readonly ITestOutputHelper _testOutputHelper;
    public SanityChecks(ITestOutputHelper testOutputHelper)
    {
        _testOutputHelper = testOutputHelper;
    }
    public class TestClass 
    {
        [JsonIgnore]
        public Dictionary<int,string> _prvList = new Dictionary<int, string>();
        public IEnumerable<string> ListValues
        {
            get => _prvList.Select(p=> p.Value).ToList();
            set
            {
                var valArr = value.ToArray();
                for (var x = 0; x < valArr.Length; x++)
                {
                    _prvList.Add(x,valArr[x]);
                }
            }
        }
    }
    [Fact]
    public void SanityCheck_CanDeserialize()
    {
        var assumption = "{\"ListValues\":[\"TestValue\",\"AAA\"]}";
        var actual = JsonConvert.DeserializeObject<TestClass>(assumption);
        Assert.Equal(2, actual._prvList.Count());
        Assert.Equal(2, actual.ListValues.Count());
    }
    [Fact]
    public void SanityCheck_CanSerialize()
    {
        var assumption = new TestClass() { ListValues = new[] { "TestValue", "AAA" } };
        var actualSerialized = JsonConvert.SerializeObject(assumption);
        _testOutputHelper.WriteLine(actualSerialized);
        Assert.Equal("{\"ListValues\":[\"TestValue\",\"AAA\"]}", actualSerialized);
    }
    [Fact]
    public void SanityCheck_CanDeserializeFromSerialized()
    {
        var assumption = new TestClass() { ListValues = new[] { "TestValue", "AAA" } };
        var actualSerialized = JsonConvert.SerializeObject(assumption);
        _testOutputHelper.WriteLine(actualSerialized);
        var actualDeserialized = JsonConvert.DeserializeObject<TestClass>(actualSerialized);
        Assert.Equal(2, actualDeserialized._prvList.Count());
        var actualDeserializedSerialized = JsonConvert.SerializeObject(actualDeserialized);
        _testOutputHelper.WriteLine(actualDeserializedSerialized);
        Assert.Equal(actualSerialized, actualDeserializedSerialized);
    }
}

如果您对如何检索此结果有任何建议,我愿意。我使用 XUnit 进行测试,但是小提琴快速实现了下面的测试,稍作修改使其成为控制台应用程序。

我尝试在对象中实现ISerializable,但遇到了同样的问题。

注意的奇怪之处:删除 IEnumerable 中的 Get 子句会导致反序列化工作(序列化不再工作)

编辑: 为了更加清楚,我需要将 int,string 配对的映射序列化为字符串列表,并且我需要将相同的序列化版本反序列化为 int,string 配对的集合。

对于小提琴:不应该抛出异常 对于 XUnit:所有测试都应该通过。

【问题讨论】:

  • 非常不清楚您的问题是什么-感谢您提供minimal reproducible example,但是您仍然需要解释您想要实现的目标...无法实现您想要的代码不能单独显示那个(即在这种特殊情况下,非常不清楚您希望如何反序列化只读类型)。
  • @AlexeiLevenkov 谢谢!我会添加一些清晰度,我以为我很清楚。我需要一个私有的 配对被外部序列化为字符串列表(忽略 int)并从字符串列表反序列化为 配对(基于索引生成的 INT) 两个对象都没有被读取- 仅限。
  • 看看this answerIEnumerable 的默认序列化使用 List,但您的测试代码构建 arrays。因此,生成的 JSON 可能包含 "$type": "System.String[], mscorlib" 以表明它是数组类型,而不是 List。我自己没有运行过你的测试,但我怀疑这可能是它失败的原因,因为你的硬编码 JSON 字符串不包含数组类型。该链接中的另一个答案建议使用属性[JsonProperty(TypeNameHandling = TypeNameHandling.None)] 来避免这种情况。
  • @SeanSkelly 我尝试了提供的解决方案 [JsonProperty(TypeNameHandling = TypeNameHandling.None)] 相同的结果。我决定尝试文档中引用的其他一些接口,相同的复制结果:(尽管读得很好!查看引用的溢出页面,它处理了 IList、List 和对象数组的行为差异,原因是序列化为数组,但预期类型是 List。根据相同的文档,它需要 IEnumerable 实现,在定义的对象内。
  • set { var valArr = value.ToArray(); for (var x = 0; x &lt; valArr.Length; x++) { _prvList.Add(x,valArr[x]); } } 很奇怪。看起来您只允许调用一次设置器(因为使用相同的数据调用它两次将返回由于Add 而导致的第二次调用异常)?如果键始终是索引,为什么不使用List 作为后备存储(因为它内置了索引)?

标签: c# json serialization json.net deserialization


【解决方案1】:

反序列化后ListValues枚举为空的原因有以下几个原因:

  1. 默认情况下,Json.Net 将在反序列化期间重用现有对象值,而不是创建新对象值。因此,对于 ListValues 等属性,它会首先调用访问器,然后在找到现有列表后,继续从 JSON 中填充它。
  2. ListValues 的访问器不会每次都返回相同的列表实例;它总是从 _prvList 支持字段创建一个新实例。

因此,在反序列化过程中会发生以下情况:

  1. 序列化程序调用ListValues 访问器,它返回从字典创建的新空列表。
  2. 序列化程序从 JSON 填充该列表。
  3. 填充的列表被丢弃(序列化程序假定TestClass 实例已经具有对列表的引用,因此它永远不会调用mutator)。
  4. 当测试稍后调用ListValues 访问器时,它会返回一个再次从字典创建的新空列表。

您可以通过将ObjectCreationHandling 设置为Replace 来更改序列化程序的行为。在反序列化期间应用此设置可让您的测试通过。小提琴here.

【讨论】:

  • 感谢您提供信息丰富且极其准确的答案(它还解释了上述奇怪之处以及其他一些未记录的内容)。我没有使用逐字提供的解决方案,而是为 ListValues 属性分配了一个属性(仍然是 ObjectCreationHandling)。感谢您的帮助!
猜你喜欢
  • 2011-10-05
  • 2017-10-23
  • 1970-01-01
  • 1970-01-01
  • 2019-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多