Json.net - 填充字典时如何保留字典值引用?

2023-11-29

我想从 JSON 文件填充字典中包含的对象,同时保留对象引用本身。

Json.net 关于 PreserveReferencesHandling 的文档明确指出,如果类型实现 System.Runtime.Serialization.ISerialized,它将不起作用:

指定参考处理选项 Newtonsoft.Json.JsonSerializer。请注意,引用不能 当通过非默认构造函数设置值时保留,例如 实现 System.Runtime.Serialization.ISerialized 的类型。

这是我失败的代码:

class Model
{
   public int Val { get; set; } = 123;
}

...

    var model = new Model();
    var to_serialize = new Dictionary<int, Model> { { 0, model } }; // works ok with list<Model>

    // serialize
    var jsonString = JsonConvert.SerializeObject(to_serialize, Formatting.Indented);

    var jsonSerializerSettings = new JsonSerializerSettings();
    jsonSerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
    jsonSerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.All; // does not work for ISerializable
    
    Assert.AreSame(to_serialize[0], model); // ok!

    JsonConvert.PopulateObject(
        value: jsonString,
        target: to_serialize,
        settings: jsonSerializerSettings
    );

    Assert.AreSame(to_serialize[0], model); // not ok... works ok with list<Model>

我的主要要求是,当调用 PopulateObject() 时,不会调用 Model 类的构造函数。相反,只有其内部字段将使用 JSON 中的值进行更新。 在我的实际情况中,Model 类包含 JSON 中不存在且我不想丢失的其他值:

[JsonObject(MemberSerialization.OptIn)]
class Model
{
   [JsonProperty(PropertyName = "val_prop")]
   public int Val { get; set; } = 123;

   // not in the json file, would like this field to maintain the value
   // it had prior to PopulateObject()
   public int OtherVal { get; set; } = 456;
}

有办法让这项工作发挥作用吗?


您的问题与来自的问题类似JsonSerializer.CreateDefault().Populate(..) 重置我的值:您想要填充一个预先存在的集合,特别是一个Dictionary<int, T>对于一些T,并填充预先存在的值。不幸的是,对于字典,Json.NET 将replace值而不是填充它们,如中所示JsonSerializerInternalReader.PopulateDictionary()它只是将值反序列化为适当的类型,并将其设置为字典。

要解决此限制,您可以创建一个custom JsonConverter for Dictionary<TKey, TValue> when TKey是一个原始类型并且TValue是一种复杂类型,它将传入的 JSON 键/值对合并到预先存在的字典中。下面的转换器可以解决这个问题:

public class DictionaryMergeConverter : JsonConverter
{
    static readonly IContractResolver defaultResolver = JsonSerializer.CreateDefault().ContractResolver;
    readonly IContractResolver resolver = defaultResolver;

    public override bool CanConvert(Type objectType)
    {
        var keyValueTypes = objectType.GetDictionaryKeyValueType();
        if (keyValueTypes == null)
            return false;
        var keyContract = resolver.ResolveContract(keyValueTypes[0]);
        if (!(keyContract is JsonPrimitiveContract))
            return false;
        var contract = resolver.ResolveContract(keyValueTypes[1]);
        return contract is JsonContainerContract;
        // Also possibly check whether keyValueTypes[1] is a read-only collection or dictionary.
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        if (reader.TokenType != JsonToken.StartObject)
            throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
        IDictionary dictionary = existingValue as IDictionary ?? (IDictionary)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        var keyValueTypes = objectType.GetDictionaryKeyValueType();
        while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
        {
            switch (reader.TokenType)
            {
                case JsonToken.PropertyName:
                    var name = (string)reader.Value;
                    reader.ReadToContentAndAssert();

                    // TODO: DateTime keys and enums with overridden names.
                    var key = (keyValueTypes[0] == typeof(string) ? (object)name : Convert.ChangeType(name, keyValueTypes[0], serializer.Culture));
                    var value = dictionary.Contains(key) ? dictionary[key] : null;

                    // TODO:
                    //  - JsonConverter active for valueType, either in contract or in serializer.Converters
                    //  - NullValueHandling, ObjectCreationHandling, PreserveReferencesHandling, 

                    if (value == null)
                    {
                        value = serializer.Deserialize(reader, keyValueTypes[1]);
                    }
                    else
                    {
                        serializer.Populate(reader, value);
                    }
                    dictionary[key] = value;
                    break;

                default:
                    throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
            }
        }

        return dictionary;
    }

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

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

public static partial class JsonExtensions
{
    public static JsonReader ReadToContentAndAssert(this JsonReader reader)
    {
        return reader.ReadAndAssert().MoveToContentAndAssert();
    }

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

public static class TypeExtensions
{
    public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
    {
        while (type != null)
        {
            yield return type;
            type = type.BaseType;
        }
    }

    public static Type[] GetDictionaryKeyValueType(this Type type)
    {
        return type.BaseTypesAndSelf().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>)).Select(t => t.GetGenericArguments()).FirstOrDefault();
    }
}

这样做后,您将遇到第二个问题:Json.NET 永远不会使用自定义转换器来填充根对象。要解决此问题,您需要致电JsonConverter.ReadJson()直接从一些实用方法:

public static partial class JsonExtensions
{
    public static void PopulateObjectWithConverter(string value, object target, JsonSerializerSettings settings)
    {
        if (target == null || value == null)
            throw new ArgumentNullException();
        var serializer = JsonSerializer.CreateDefault(settings);
        var converter = serializer.Converters.Where(c => c.CanConvert(target.GetType()) && c.CanRead).FirstOrDefault() ?? serializer.ContractResolver.ResolveContract(target.GetType()).Converter;
        using (var jsonReader = new JsonTextReader(new StringReader(value)))
        {
            if (converter == null)
                serializer.Populate(jsonReader, target);
            else
            {
                jsonReader.MoveToContentAndAssert();
                var newtarget = converter.ReadJson(jsonReader, target.GetType(), target, serializer);
                if (newtarget != target)
                    throw new JsonException(string.Format("Converter {0} allocated a new object rather than populating the existing object {1}.", converter, value));
            }
        }
    }
}

您现在可以按如下方式填充词典:

var jsonString = JsonConvert.SerializeObject(to_serialize, Formatting.Indented);

var settings = new JsonSerializerSettings
{
    Converters = { new DictionaryMergeConverter() },
};
JsonExtensions.PopulateObjectWithConverter(jsonString, to_serialize, settings);

Notes:

  • PreserveReferencesHandling对于是否填充或替换字典值没有影响。相反,此设置控制具有对同一对象的多个引用的序列化图在往返时是否保持其引用拓扑。

  • 在你的问题中你写了// works ok with list<Model>但事实上这是不正确的。当一个List<T>已填充新值是appended到列表中,所以Assert.AreSame(to_serialize[0], model);纯粹是靠运气过去的。如果你另外断言Assert.AreSame(1, to_serialize.Count)它会失败的。

  • 虽然转换器适用于原始键,例如string and int它可能不适用于需要 JSON 特定转换的键类型,例如enum or DateTime.

  • 该转换器目前仅适用于Dictionary<TKey, TValue>并利用此类型实现非泛型的事实IDictionary界面。它可以扩展到其他字典类型,例如SortedDictionary<TKey,TValue>如果需要。

演示小提琴here.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Json.net - 填充字典时如何保留字典值引用? 的相关文章

  • 将 2D 数组映射到 1D 数组

    我想用一维数组来表示一个二维数组 函数将传递两个索引 x y 和要存储的值 这两个索引代表一维数组的单个元素 并相应地设置它 我知道一维数组需要具有 arrayWidth arrayHeight 的大小 但我不知道如何设置每个元素 例如 如
  • 如何在 C# 事件中区分更改是由代码还是由用户进行?

    我有一个简单的TextBox一开始是空的 我有一个简单的事件 TextChanged 可以知道用户何时更改了其中的任何内容TextBox 但是 如果我自己在代码中对其执行任何操作 该事件就会触发 喜欢设置textbox Text Test
  • boost线程在中断时不打印退出消息

    我有这段代码用于执行三个线程 其中第二个线程应在按 Enter 时中断并打印退出消息 void input val DO STUFF return void process val DO STUFF try cout lt lt waiti
  • Linq Where 本地计数器关闭在 VS watch 中的结果不同

    我尝试删除前 3 个元素array与 LinQWhere扩展功能 这是一个例子 var array new 1 2 3 4 5 6 7 8 9 var count 3 var deletedTest1 0 var test1 array W
  • 字节到二进制字符串 C# - 显示所有 8 位数字

    我想在文本框中显示一个字节 现在我正在使用 Convert ToString MyVeryOwnByte 2 但是 当字节开头有 0 时 这些 0 就会被删除 例子 MyVeryOwnByte 00001110 Texbox shows g
  • 对数字进行向上和向下舍入 C++

    我试图让我的程序分别向上和向下舍入数字 例如 如果数字是3 6 我的程序应该四舍五入最接近的数字 4 如果该数字是3 4 它将向下舍入为 3 我尝试使用ceil库获取 3 个项目的平均值 results ceil marks1 marks2
  • 阅读 Stack Overflow RSS 源

    我正在尝试获取未回答问题的列表the feed https stackoverflow com feeds 但我在阅读时遇到困难 const string RECENT QUESTIONS https stackoverflow com f
  • C++ 在 Vector 中使用不可分配的对象

    我想将对象列表存储在std vector 但对象包含引用且无法分配给 但是 我可以复制构造该对象 我能想到的唯一选择是使用指针来包装对象并在需要分配指针时重新设置指针 但这样做的语法会显着降低可读性 特别是在使用迭代器时 我更喜欢另一种选择
  • 如何避免选择项目时 winforms 树视图图标发生变化

    我正在一个小型 C Winforms 应用程序中尝试树视图 我已经以编程方式将 ImageList 分配给树视图 并且所有节点都很好地显示了它们的图标 but当我单击一个节点时 它的图标会发生变化 变为 ImageList 中的第一个图像
  • glDrawElements 只绘制半个四边形

    这是我的功能 void Object draw2 if mIsInitialised return Tell OpenGL about our vertex and normal data glEnableClientState GL VE
  • 当格式字符串包含“{”时,String.Format 异常

    我正在使用 VSTS 2008 C Net 2 0 执行以下语句时 String Format 语句抛出 FormatException 有什么想法是错误的吗 这是获取我正在使用的 template html 的位置 我想在 templat
  • C#:如何使用 SHOpenFolderAndSelectItems [重复]

    这个问题在这里已经有答案了 有人可以举例说明如何使用 shell 函数吗SH打开文件夹并选择项目 http msdn microsoft com en us library bb762232 VS 85 aspx来自 C 我不太明白如何使用
  • C++ Primer 5th Edition 错误 bool 值没有指定最小大小?

    bool 的最小大小不应该是 1 个字节吗 这有点学术性的东西 尽管它们会转换为数字 并且 与其他所有事物一样 它们最终将基本上由计算机内存中的数字表示 但布尔值不是数字 你的bool可以取值true 或值false 即使您确实需要至少 1
  • 在可观察项目生成时对其进行处理

    我有一个IObservable它会生成一次性物品 并且在其生命周期内可能会生成无限数量的物品 因此 我想在每次生成新项目时处理最后一个项目 因此Using http reactivex io documentation operators
  • C# ToString("MM/dd/yy") 删除前导 0 [重复]

    这个问题在这里已经有答案了 可能的重复 格式化 NET DateTime Day 不带前导零 https stackoverflow com questions 988353 format net datetime day with no
  • 为什么我不能在扩展 List 的类中调用 OrderBy?

    我有一堂课 Deck 其中包含一个名为的方法Shuffle 我正在致力于重构Deck延长List
  • 改进C++逐行读取文件的能力?

    我正在解析大约 500GB 的日志文件 我的 C 版本需要 3 5 分钟 我的 Go 版本需要 1 2 分钟 我正在使用 C 的流来流式传输文件的每一行以进行解析 include
  • 从 C# 中的 .NET SecureString 读取单个字符?

    WPF 的PasswordBox 返回一个SecureString 它对窥探者隐藏密码 问题是你最终必须获得密码的值 而我在网上找到的建议都涉及将值复制到字符串中 这会让你回到窥探者的问题 IntPtr bstr Marshal Secur
  • 使用来自Processing-JS的JSON

    我想使用编写一个应用程序处理 JS http processingjs org 并且我希望能够使用服务器端数据加载它 我还没有编写服务器端 所以我可以使用任何东西 但似乎明显的 AJAX 事情是使用 JSON 将数据上传到页面中 如何从我的
  • 加载腌制字典对象或加载 JSON 文件哪个更快? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 什么更快 A Unpickling 加载 一个 pickled 字典对象 使用pickle load or B 使用以下命令将 JSON

随机推荐

  • 如何将二维数组传递给C中的函数?

    我的函数原型是 int rotate int arr int row int col int fl where arr是二维数组 row and col分别是二维数组的行数和列数 fl是一个标志变量 如果值fl is 0那么数组将向右旋转
  • ipython:操作错误:磁盘 I/O 错误

    到目前为止 我一直在 fedora 18 上成功运行 ipython 尝试启动它时出现以下异常 Traceback most recent call last File usr bin ipython line 9 in
  • 无法使用jsoup登录网站

    我正在尝试登录这个网站 这是我到目前为止所尝试过的 但它似乎不起作用 try Connection Response login Jsoup connect login url method Connection Method GET ex
  • Grafana 世界地图:表数据源

    我正在尝试使用 grafana 的世界地图插件 以表数据作为数据源 在我的例子中为 influxdb 该插件得到了最新版本的支持 link 但遗憾的是我无法让它发挥作用 我使用与插件文档中相同的查询 但我的世界地图中没有显示任何点 以下是我
  • 如何将 Qline 坐标发送到 QPainter 小部件

    我创建了一个 Qwidget Form temp 它根据父窗口小部件 MainWindow 中创建的数据数组绘制线条 我面临的问题是我通过槽 send data 从 MainWindow 发送到 Form temp 的数据不被 Form t
  • 根据需要设置 reCaptcha 字段

    我使用新的 reCaptchajQuery 验证插入 验证插件适用于我的表单 但不幸的是它不适用于 reCaptcha 我尝试让它工作如下 HTML div class g recaptcha div JavaScript recaptch
  • 为什么我不能创建这样的 numpy 数组: array([1, 2], 3)

    from numpy import array test list 1 2 3 x array test list ValueError setting an array element with a sequence 基本上 我有一个带有
  • 如何访问 iFrame 中的 DOM 元素

    我正在编写一个 jQuery 插件 它需要能够针对 iFrame 中的 DOM 元素运行 我现在只是在本地测试这个 即 url 是 file example html 在 Chrome 中我一直点击 SecurityError 无法从 HT
  • 在 C# 中将 MM:SS 字符串转换为 HH:MM:SS

    我有这段代码可以很好地将 HH MM SS 转换为整数秒 for int i 0 i lt nrdaily Rows Count i double NRT TimeSpan Parse nrdaily Rows i 3 ToString T
  • 从 WiX 更改 XML 节点值

    我希望能够从 WiX 更改 XML 节点值 XML 结构如下所示
  • SSIS正在成功执行,但它似乎没有运行脚本任务部分

    我有一个 SSIS 包 它运行 SQL 查询并通过数据流任务将其导出到 csv 文件 创建 csv 后 我设置了一个 脚本任务 来连接到 SMTP 服务器并将 csv 文件作为附件发送 在我的本地计算机上 该包运行良好 但是当我将其加载到服
  • 未指定网络安全配置,使用平台默认值 - Android 日志

    我正在尝试通过 000webhost com 创建数据库 每当我从 android studio 运行应用程序时 我都会在事件日志中看到此消息 有谁知道如何解决这个问题 非常感激 我也有同样的问题 请将此行添加到清单中的应用程序标记中 我希
  • Espresso 不会等待 ViewPager 上的滑动操作完成

    Espresso 宣传的特点是它总是等待 Android 的 UI 线程空闲 这样你就不必处理任何计时问题 但我似乎发现了一个例外 设置是一个ViewPager与EditText在每个片段中 我想要 Espresso 将文本输入到EditT
  • 将系列转换为 Pandas DateTime [重复]

    这个问题在这里已经有答案了 D 10Aug49 21Jan45 15Sep47 13Jun52 将其转换为 pandas 日期 确保年份是 1900 年而不是 2000 年 到目前为止 我有这段代码可以转换并打印 pandas 日期 但世纪
  • Session_End 可以在窗口关闭时触发吗? (ASP.NET)

    我在网站上放置了一个 在线 计数器 并且遇到了这两个相互矛盾的来源 这个 我正在使用这个示例代码 http aspdotnetfaq com Faq How to show number of online users visitors f
  • Yii2 速率限制 API

    我担心 Yii2 速率限制 api 什么是速率限制 api 为什么使用它 这里有一些方法来自Yii2yii 大师能否用简单的话解释一下这些方法 我应该在何时何地在我的 api 中使用速率限制 public function getRateL
  • 如何改变变量赋值的较低索引?

    我想对具有较低索引的变量进行赋值 这就是我想做的 int i logic 63 0 data i someCalculatedNumber data 63 i 8 h0 我知道这不会编译 完成这项任务的最佳方法是什么 如果您希望将 LSB
  • 快速获取服务器时间

    有没有办法让服务器时间与 swift 我想根据服务器设置静态时间 这样 即使用户更改时区和日期 也没关系 视图控制器上显示的时间将是基于服务器的时间而不是 NSDate let today NSDate println today 目前 如
  • 对 pandas 列的多个子集高效运行回归分析

    我本可以选择一个较短的问题 只关注这里的核心问题 即列表排列 但我带来的原因统计模型 and pandas问题是 可能存在用于逐步回归的特定工具 同时具有存储所需回归输出的灵活性 就像我将在下面向您展示的那样 但效率更高 至少我希望如此 给
  • Json.net - 填充字典时如何保留字典值引用?

    我想从 JSON 文件填充字典中包含的对象 同时保留对象引用本身 Json net 关于 PreserveReferencesHandling 的文档明确指出 如果类型实现 System Runtime Serialization ISer