Newtonsoft JSON PreserveReferences处理自定义等于用法

2024-05-18

我目前在使用 Newtonsoft Json 时遇到一些问题。

我想要的很简单:将要序列化的对象与所有属性和子属性进行比较以确保相等。

我现在尝试创建自己的 EqualityComparer,但它仅与父对象的属性进行比较。

另外,我尝试编写自己的ReferenceResolver,但没有成功。

让我们用一个例子来谈谈:

public class EntityA
{
    int Foo {get; set;}

    public override bool Equals(object obj)
    {
        return (obj is EntityA other) && other.Foo == this.Foo;
    }
}

public class EntityB
{
    int Bar {get; set;}

    EntityA Parent {get; set;}

    public override bool Equals(object obj)
    {
        return (obj is EntityB other) && other.Bar == this.Bar;
    }
}

public class InnerWrapper
{
    public string FooBar {get; set;}

    public EntityB BEntity {get; set;}
}

public class OuterClass
{
    public EntityA AEntity { get; set;}

    List<InnerWrapper> InnerElements {get; set;}
}    

现在我想要的是从 EntityB 到 EntityA 的引用。就我而言,它们总是一样的。所以我期望的是,在每个 EntityB 的 JSON 中,对 EntityA 的引用都写为 ref。实体的 Equal 会覆盖 Equals 以检查它们是否相同。它们是数据库对象,因此只要它们的 ID 相同,它们就相等。在这种情况下,我给他们打电话Foo and Bar.

我尝试过的如下:

public class MyEqualComparer : IEqualityComparer
{
    public bool Equals(object x, object y)
    {
        return x.Equals(y);
    }

    public int GetHashCode(object obj)
    {
        return obj.GetHashCode();
    }
}

具有以下 JSON 设置

public static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    NullValueHandling = NullValueHandling.Ignore,
    FloatParseHandling = FloatParseHandling.Decimal,
    Formatting = Formatting.Indented,
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    EqualityComparer = new MyEqualComparer(),
    ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
    Error = (sender, args) => Log.Error(args.ErrorContext.Error, $"Error while (de)serializing: {args.ErrorContext}; object: {args.CurrentObject}")
};

但这不起作用。它比较完全错误的值。例如来自的 EntityAOuterClass与每个InnerWrapper。但不适用于属性甚至子属性(在本例中为属性)EntityB of the InnerWrapper).

对于自定义的ReferenceResolver,我也没有运气,因为上面的设置确实很通用,我不知道如何编写通用的设置。

您知道如何完成这项工作吗?

// Edit:

下面是我期望的示例:

{
    "$id" : "1",
    "AEntity": {
        "$id": "2",
        "Foo": 200
    },
    "InnerElements": [
        {
            "$id": "3",
            "Bar": 20,
            "Parent": {
                "$ref" : "2"
            }
        },
        {
            "$id": "4",
            "Bar": 21,
            "Parent": {
                "$ref" : "2"
            }
        },
        {
            "$id": "5",
            "Bar": 23,
            "Parent": {
                "$ref" : "2"
            }
        },
        {
            "$id": "6",
            "Bar": 24,
            "Parent": {
                "$ref" : "2"
            }
        },
        {
            "$id": "7",
            "Bar": 25,
            "Parent": {
                "$ref" : "2"
            }
        }
    ]

}

这就是我得到的:

    {
    "$id" : "1",
    "AEntity": {
        "$id": "2",
        "Foo": 200
    },
    "InnerElements": [
        {
            "$id": "3",
            "Bar": 20,
            "Parent": {
                "$id": "8",
                "Foo": 200
            }
        },
        {
            "$id": "4",
            "Bar": 21,
            "Parent": {
                "$id": "9",
                "Foo": 200
            }
        },
        {
            "$id": "5",
            "Bar": 23,
            "Parent": {
                "$id": "10",
                "Foo": 200
            }
        },
        {
            "$id": "6",
            "Bar": 24,
            "Parent": {
                "$id": "11",
                "Foo": 200
            }
        },
        {
            "$id": "7",
            "Bar": 25,
            "Parent": {
                "$id": "12",
                "Foo": 200
            }
        }
    ]

}

当然,在这种情况下,影响是很小的。但我的真实场景要大得多。


如中所述这个答案 https://stackoverflow.com/a/25573204 to JSON.NET 序列化 - DefaultReferenceResolver 如何比较相等性? https://stackoverflow.com/q/25567814 by 安德鲁·惠特克 https://stackoverflow.com/users/497356/andrew-whitaker,Json.NET 在通过以下方式保留引用时专门使用引用相等性:PreserveReferencesHandling https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_PreserveReferencesHandling.htm。那个设定JsonSerializerSettings.EqualityComparer https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_JsonSerializerSettings_EqualityComparer.htm用于参考循环检测而不是参考保存和解析,如中所述这个答案 https://stackoverflow.com/a/46938352/3744182 to 为什么引用循环检测不使用引用相等? https://stackoverflow.com/q/46936395/3744182.

安德鲁的回答给出了一个自定义的例子IReferenceResolver https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_IReferenceResolver.htm使用特定类型对象的对象相等来解析引用,并假设all序列化对象就是这种类型。您想要做的是仅对某些类型使用对象相等(EntityA and EntityB)并依靠 Json.NET默认参考解析器 https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs对于所有其他类型。

您可以通过以下方式完成此操作装饰器模式 https://en.wikipedia.org/wiki/Decorator_pattern,其中您将 Json.NET 的引用解析器的实例包装在您自己的中IReferenceResolver。然后实现需要自己的自定义相等比较的类型所需的任何逻辑,并将其他所有内容传递给底层默认解析器。

这是满足您要求的一种:

public class SelectiveValueEqualityReferenceResolver : EquivalencingReferenceResolver
{
    readonly Dictionary<Type, Dictionary<object, object>> representatives;

    public SelectiveValueEqualityReferenceResolver(IReferenceResolver defaultResolver, IEnumerable<Type> valueTypes)
        : base(defaultResolver)
    {
        if (valueTypes == null)
            throw new ArgumentNullException();
        representatives = valueTypes.ToDictionary(t => t, t => new Dictionary<object, object>());
    }

    protected override bool TryGetRepresentativeObject(object obj, out object representative)
    {
        var type = obj.GetType();
        Dictionary<object, object> typedItems;

        if (representatives.TryGetValue(type, out typedItems))
        {
            return typedItems.TryGetValue(obj, out representative);
        }
        return base.TryGetRepresentativeObject(obj, out representative);
    }

    protected override object GetOrAddRepresentativeObject(object obj)
    {
        var type = obj.GetType();
        Dictionary<object, object> typedItems;

        if (representatives.TryGetValue(type, out typedItems))
        {
            object representative;
            if (!typedItems.TryGetValue(obj, out representative))
                representative = (typedItems[obj] = obj);
            return representative;

        }
        return base.GetOrAddRepresentativeObject(obj);
    }
}

public abstract class EquivalencingReferenceResolver : IReferenceResolver
{
    readonly IReferenceResolver defaultResolver;

    public EquivalencingReferenceResolver(IReferenceResolver defaultResolver)
    {
        if (defaultResolver == null)
            throw new ArgumentNullException();
        this.defaultResolver = defaultResolver;
    }

    protected virtual bool TryGetRepresentativeObject(object obj, out object representative)
    {
        representative = obj;
        return true;
    }

    protected virtual object GetOrAddRepresentativeObject(object obj)
    {
        return obj;
    }

    #region IReferenceResolver Members

    public void AddReference(object context, string reference, object value)
    {
        var representative = GetOrAddRepresentativeObject(value);
        defaultResolver.AddReference(context, reference, representative);
    }

    public string GetReference(object context, object value)
    {
        var representative = GetOrAddRepresentativeObject(value);
        return defaultResolver.GetReference(context, representative);
    }

    public bool IsReferenced(object context, object value)
    {
        object representative;

        if (!TryGetRepresentativeObject(value, out representative))
            return false;
        return defaultResolver.IsReferenced(context, representative);
    }

    public object ResolveReference(object context, string reference)
    {
        return defaultResolver.ResolveReference(context, reference);
    }

    #endregion
}

然后您将按如下方式使用:

var settings = new JsonSerializerSettings
{
    //Commented out TypeNameHandling since the JSON in the question does not include type information
    //TypeNameHandling = TypeNameHandling.All,
    NullValueHandling = NullValueHandling.Ignore,
    FloatParseHandling = FloatParseHandling.Decimal,
    Formatting = Formatting.Indented,
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
    ReferenceResolverProvider = () => new SelectiveValueEqualityReferenceResolver(
        new JsonSerializer().ReferenceResolver, 
        new [] { typeof(EntityA), typeof(EntityB) }),
    Error = (sender, args) => Log.Error(args.ErrorContext.Error, $"Error while (de)serializing: {args.ErrorContext}; object: {args.CurrentObject}")
};

var outer = JsonConvert.DeserializeObject<OuterClass>(jsonString, settings);

var json2 = JsonConvert.SerializeObject(outer, settings);

请注意,我必须对您的类型进行各种修复才能使其正常工作:

public static class EqualityHelper
{
    public static bool? EqualsQuickReject<T1, T2>(T1 item1, T2 item2) 
        where T1 : class
        where T2 : class
    {
        if ((object)item1 == (object)item2)
            return true;
        else if ((object)item1 == null || (object)item2 == null)
            return false;

        if (item1.GetType() != item2.GetType())
            return false;

        return null;
    }
}

public class EntityA : IEquatable<EntityA> //Fixed added IEquatable<T>
{
    public int Foo { get; set; } // FIXED made public

    public override bool Equals(object obj)
    {
        return Equals(obj as EntityA);
    }

    // Fixed added required GetHashCode() that is compatible with Equals()
    public override int GetHashCode()
    {
        return Foo.GetHashCode();
    }

    #region IEquatable<EntityA> Members

    public bool Equals(EntityA other)
    {
        // FIXED - ensure Equals is reflexive, symmetric and transitive even when dealing with derived types
        var initial = EqualityHelper.EqualsQuickReject(this, other);
        if (initial != null)
            return initial.Value;
        return this.Foo == other.Foo;
    }

    #endregion
}

public class EntityB : IEquatable<EntityB> //Fixed added IEquatable<T>
{
    public int Bar { get; set; } // FIXED made public

    public EntityA Parent { get; set; } // FIXED made public

    public override bool Equals(object obj)
    {
        return Equals(obj as EntityB);
    }

    // Fixed added required GetHashCode() that is compatible with Equals()
    public override int GetHashCode()
    {
        return Bar.GetHashCode();
    }

    #region IEquatable<EntityB> Members

    public bool Equals(EntityB other)
    {
        // FIXED - ensure Equals is reflexive, symmetric and transitive even when dealing with derived types
        var initial = EqualityHelper.EqualsQuickReject(this, other);
        if (initial != null)
            return initial.Value;
        return this.Bar == other.Bar;
    }

    #endregion
}

public class InnerWrapper
{
    public string FooBar { get; set; }

    public EntityB BEntity { get; set; }
}

public class OuterClass
{
    public EntityA AEntity { get; set; }

    public List<EntityB> InnerElements { get; set; }//FIXED -- made public and corrected type to be consistent with sample JSON
}

Notes:

  • SelectiveValueEqualityReferenceResolver工作原理如下。构造时,它会被赋予一个默认的引用解析器和一个要使用对象相等性的类型列表。然后,当其中之一IReferenceResolver调用方法时,它会检查传入的对象是否属于自定义类型之一。如果是,它会使用对象相等性检查是否已经遇到相同类型的等效对象。如果是,则将该初始对象传递给默认引用解析器。如果不是,它将缓存传入对象作为对象等效对象的定义实例,然后将传入对象传递到默认引用解析器。

  • 上述逻辑仅在被覆盖时才有效object.Equals()是一个适当的等价关系 https://en.wikipedia.org/wiki/Equivalence_relation——即自反性、对称性和传递性。

    在您的代码中,如果EntityA or EntityB曾经被细分。因此我修改了你的Equals()方法要求传入对象具有相同类型,而不仅仅是兼容类型。

  • When Equals()被覆盖了,还需要覆盖以兼容的方式,使得相同的对象具有相同的哈希码。

    这在您的代码中没有完成,因此我添加了必要的逻辑EntityA and EntityB.

  • Json.NET 的DefaultReferenceResolver https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs是内部的,所以我不得不使用一种稍微有点hacky的方法来创建一个,即构造一个临时的JsonSerializer并抓住它的ReferenceResolver.

  • SelectiveValueEqualityReferenceResolver不是线程安全的,因此应在每个线程中使用新的序列化器实例。

  • SelectiveValueEqualityReferenceResolver旨在生成相同的$id序列化期间对象相等对象的值。它的设计初衷并不是为了将相同的对象与不同的对象合并$id反序列化期间将值转换为引用相等的对象。我认为如果需要的话可以添加。

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

Newtonsoft JSON PreserveReferences处理自定义等于用法 的相关文章

  • C++:无法使用scoped_allocator_adaptor传播polymorphic_allocator

    我有一个vector
  • 模板类的不明确多重继承

    我有一个真实的情况 可以总结为以下示例 template lt typename ListenerType gt struct Notifier void add listener ListenerType struct TimeListe
  • 如何在C++中实现模板类协变?

    是否可以以这样一种方式实现类模板 如果模板参数相关 一个对象可以转换为另一个对象 这是一个展示这个想法的例子 当然它不会编译 struct Base struct Derived Base template
  • fgets() 和 Ctrl+D,三次才能结束?

    I don t understand why I need press Ctrl D for three times to send the EOF In addition if I press Enter then it only too
  • 为什么 POSIX 允许在只读模式下超出现有文件结尾 (fseek) 进行搜索

    为什么寻找文件结尾很有用 为什么 POSIX 让我们像示例中那样在以只读方式打开的文件中进行查找 c http en cppreference com w c io fseek http en cppreference com w c io
  • 为什么禁止在 constexpr 函数中使用 goto?

    C 14 对你能做什么和不能做什么有规则constexpr功能 其中一些 没有asm 没有静态变量 看起来相当合理 但标准也不允许goto in constexpr功能 即使它允许其他控制流机制 这种区别背后的原因是什么 我以为我们已经过去
  • 使用 C# 在 WinRT 中获取可用磁盘空间

    DllImport kernel32 dll SetLastError true static extern bool GetDiskFreeSpaceEx string lpDirectoryName out ulong lpFreeBy
  • 写入和读取文本文件 - C# Windows 通用平台应用程序 Windows 10

    有用 但在显示任何内容之前 您必须在文本框中输入内容 我想那是因为我使用了 TextChanged 事件处理程序 如果我希望它在没有用户交互的情况下显示文本文件的内容 我应该使用哪个事件处理程序 因此 我想在按下按钮时将一些数据写入 C W
  • 在 ASP.Net Core 2.0 中导出到 Excel

    我曾经使用下面的代码在 ASP NET MVC 中将数据导出到 Excel Response AppendHeader content disposition attachment filename ExportedHtml xls Res
  • 编译的表达式树会泄漏吗?

    根据我的理解 JIT 代码在程序运行时永远不会从内存中释放 这是否意味着重复调用 Compile 表达式树上会泄漏内存吗 这意味着仅在静态构造函数中编译表达式树或以其他方式缓存它们 这可能不那么简单 正确的 他们可能是GCed Lambda
  • 我的 strlcpy 版本

    海湾合作委员会 4 4 4 c89 我的程序做了很多字符串处理 我不想使用 strncpy 因为它不会终止 我不能使用 strlcpy 因为它不可移植 只是几个问题 我怎样才能让我的函数正常运行 以确保它完全安全稳定 单元测试 这对于生产来
  • 像“1$”这样的位置参数如何与 printf() 一起使用?

    By man I find printf d width num and printf 2 1 d width num 是等价的 但在我看来 第二种风格应该与以下相同 printf d num width 然而通过测试似乎man是对的 为什
  • C 中的位移位

    如果与有符号整数对应的位模式右移 则 1 vacant bit will be filled by the sign bit 2 vacant bit will be filled by 0 3 The outcome is impleme
  • 作为字符串的动态属性名称

    使用 DocumentDB 创建新文档时 我想设置属性名称动态地 目前我设置SomeProperty 像这样 await client CreateDocumentAsync dbs db colls x new SomeProperty
  • 已过时 - OpenCV 的错误模式

    我正在使用 OpenCV 1 进行一些图像处理 并且对 cvSetErrMode 函数 它是 CxCore 的一部分 感到困惑 OpenCV 具有三种错误模式 叶 调用错误处理程序后 程序终止 Parent 程序没有终止 但错误处理程序被调
  • 如何构建印度尼西亚电话号码正则表达式

    这些是一些印度尼西亚的电话号码 08xxxxxxxxx 至少包含 11 个字符长度 08xxxxxxxxxxx 始终以 08 开头 我发现这个很有用 Regex regex new Regex 08 0 9 0 9 0 9 0 9 0 9
  • 在 ASP.NET 中将事件冒泡为父级

    我已经说过 ASP NET 中的层次结构 page user control 1 user control 2 control 3 我想要做的是 当控件 3 它可以是任何类型的控件 我一般都想这样做 让用户用它做一些触发回发的事情时 它会向
  • 更改显示的 DPI 缩放大小使 Qt 应用程序的字体大小渲染得更大

    我使用 Qt 创建了一些 GUI 应用程序 我的 GUI 应用程序包含按钮和单选按钮等控件 当我运行应用程序时 按钮内的按钮和字体看起来正常 当我将显示器的 DPI 缩放大小从 100 更改为 150 或 200 时 无论分辨率如何 控件的
  • C++ 成员函数中的“if (!this)”有多糟糕?

    如果我遇到旧代码if this return 在应用程序中 这种风险有多严重 它是一个危险的定时炸弹 需要立即在应用程序范围内进行搜索和销毁工作 还是更像是一种可以悄悄留在原处的代码气味 我不打算writing当然 执行此操作的代码 相反
  • 如何将字符串“07:35”(HH:MM) 转换为 TimeSpan

    我想知道是否有办法将 24 小时时间格式的字符串转换为 TimeSpan 现在我有一种 旧时尚风格 string stringTime 07 35 string values stringTime Split TimeSpan ts new

随机推荐