我目前在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。实体相等会覆盖相等以检查它们是否相同。它们是数据库对象,因此一旦ID相同,它们就相等。在这种情况下,我将它们称为Foo
和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}")
};
但是它不起作用。它比较完全错误的值。例如,来自OuterClass
和每个InnerWrapper
的EntityA。但不包含属性甚至子属性(在这种情况下,EntityB
的{{1}}的属性)。
使用自定义的ReferenceResolver,我也不走运,因为上面的设置确实是通用的,我也不知道如何编写通用的。
您是否知道如何进行这项工作?
//编辑:
下面是我期望的例子:
InnerWrapper
这就是我得到的:
{
"$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"
}
}
]
}
当然,在这种情况下,影响很小。但是我的真实情况更大。
答案 0 :(得分:2)
如this answer在JSON.NET Serialization - How does DefaultReferenceResolver compare equality?到 Andrew Whitaker 中所述,当通过PreserveReferencesHandling
保留引用时,Json.NET仅使用引用相等。设置JsonSerializerSettings.EqualityComparer
用于参考循环检测,而不是参考保存和解析,如this answer至Why doesn't reference loop detection use reference equality?中所述。
安德鲁(Andrew)的答案给出了一个自定义IReferenceResolver
的示例,该自定义default reference resolver使用对象相等性对特定类型的对象解析引用,并假定所有序列化的对象均为该类型。您只想对某些类型(EntityA
和EntityB
)使用对象相等性,而对其他所有类型都使用Json.NET的decorator pattern。
您可以通过equivalence relation完成此操作,其中您将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
}
注意:
SelectiveValueEqualityReferenceResolver
的工作方式如下。构造后,将为其提供默认的引用解析器和使用对象相等性的类型列表。然后,在调用IReferenceResolver
方法之一时,它将检查传入的对象是否属于自定义类型之一。如果是这样,它将使用对象相等性检查是否已经遇到相同类型的等效对象。如果是这样,则将该初始对象传递到默认引用解析器。否则,它将传入的对象作为对象等效对象的定义实例进行缓存,然后将传入的对象传递给默认的引用解析器。
仅当覆盖的object.Equals()
是正确的GetHashCode()
时,上述逻辑才有效-即自反,对称和可传递。
在您的代码中,如果EntityA
或EntityB
曾经被子类化,则无法保证会发生这种情况。因此,我修改了Equals()
方法,以要求传入对象具有相同的类型,而不仅仅是兼容的类型。
当覆盖Equals()
时,还必须以兼容的方式覆盖DefaultReferenceResolver
,以使相等的对象具有相同的哈希码。
您的代码未完成此操作,因此我向EntityA
和EntityB
添加了必要的逻辑。
Json.NET的Fiddle是内部的,因此我不得不使用一种有点怪异的方法来创建它,即构造一个临时JsonSerializer
并获取其ReferenceResolver
。
SelectiveValueEqualityReferenceResolver
不是线程安全的,因此应在每个线程中使用一个新的序列化程序实例。
SelectiveValueEqualityReferenceResolver
设计为在序列化期间为对象相等的对象生成相同的$id
值。它并非旨在在反序列化期间将具有不同$id
值的相等对象合并为参考相等对象。我认为可以根据需要添加。
答案 1 :(得分:1)
感谢dbc的帮助。
您的代码几乎像我想要的那样工作。在该示例中,它确实工作正常(对代码问题感到抱歉)。
如果对您的代码进行少量调整,不仅要依赖特定类型。
public class SelectiveValueEqualityReferenceResolver : EquivalencingReferenceResolver
{
private readonly Dictionary<Type, Dictionary<object, object>> _representatives;
public SelectiveValueEqualityReferenceResolver(IReferenceResolver defaultResolver)
: base(defaultResolver)
{
this._representatives = new Dictionary<Type, Dictionary<object, object>>();
}
protected override bool TryGetRepresentativeObject(object obj, out object representative)
{
var type = obj.GetType();
if (type.GetTypeInfo().IsClass && this._representatives.TryGetValue(type, out var typedItems))
return typedItems.TryGetValue(obj, out representative);
return base.TryGetRepresentativeObject(obj, out representative);
}
protected override object GetOrAddRepresentativeObject(object obj)
{
var type = obj.GetType();
if (!type.GetTypeInfo().IsClass)
return base.GetOrAddRepresentativeObject(obj);
if (!this._representatives.TryGetValue(type, out var typedItems))
{
typedItems = new Dictionary<object, object>();
this._representatives.Add(type, typedItems);
}
if (!typedItems.TryGetValue(obj, out var representative))
representative = typedItems[obj] = obj;
return representative;
}
}
该类对所有类使用默认的比较器。对于所有其他(结构等),它将使用默认值。