控制JSON .NET的参考ID生成 [英] Controlling JSON .NET's reference ID generation
问题描述
我希望能够控制JSON .NET如何生成其元引用ID,例如"$id": "1"
.输入以下代码:
I'd like to be able to control how JSON .NET generates its meta reference IDs such as "$id": "1"
. Take the following code:
public class Person
{
public string Name { get; set; }
public Person Mother { get; set; }
}
.
var settings = new Newtonsoft.Json.JsonSerializerSettings
{
PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects,
Formatting = Newtonsoft.Json.Formatting.Indented
};
Newtonsoft.Json.JsonConvert.DefaultSettings = () => settings;
var person = new Person
{
Name = "bob",
Mother = new Person { Name = "jane" }
};
var personJson = JsonConvert.SerializeObject(person);
var motherJson = JsonConvert.SerializeObject(person.Mother);
person
的JSON如下所示:
The JSON for person
looks like this:
{
"$id": "1",
"Name": "bob",
"Mother": {
"$id": "2",
"Name": "jane",
"Mother": null
}
}
但是,如果我直接序列化person.Mother
,则JSON如下所示:
However, if I serialize person.Mother
directly, the JSON looks like this:
{
"$id": "1",
"Name": "jane",
"Mother": null
}
在第一个JSON中,Jane是"$id": "2"
,但是直接序列化Jane是"$id": "1"
.这是我在正常情况下期望的行为,因为序列化程序会按照遍历对象的顺序分配ID,但是我真的很想覆盖ID的生成,以便可以将其作为对象引用本身的哈希值.这样,无论序列化为父级成员还是单独序列化,Jane每次为程序的每个运行实例生成相同的ID.
In the first JSON, Jane is "$id": "2"
, but serializing Jane directly is "$id": "1"
. This is the behavior I'd expect under normal conditions as the serializer assigns the IDs in the order it traverses the objects, but I'd really like to override the ID generation so that I could make it a hash of the object reference itself. This way, Jane would generate the same ID per running instance of the program every time regardless if serialized as a member of a parent or serialized individually.
更新
每个选定答案中的示例代码和注释中的推荐,我使用了IReferenceResolver
.事实证明,虽然我不能使用它,但是无论如何我都会在下面包含代码.之所以不起作用,是因为我试图将JSON.NET混作一个快速且肮脏的克隆工具,所以我不能指责它不适合我的需求.从那以后,我不再使用自己的自定义克隆实用程序,因此不再需要它.
Per sample code in selected answer and recommendation in comment, I have used IReferenceResolver
. It turns out that I can't use it, though, but I'll include the code below anyway. The reason why this won't work is because I am trying to bastardize JSON.NET as a quick and dirty cloning tool, so I can't fault it for not suiting my needs. I've since fallen back on my own custom cloning utility, so I no longer need this.
public class ObjectReferenceResolver : Newtonsoft.Json.Serialization.IReferenceResolver
{
readonly Dictionary<object, int> objectDic = new Dictionary<object, int>();
int maxId = 0;
//Called during serialization
public string GetReference(object context, object value)
{
//This method will return the meta $id that you choose. In this example, I am storing
//object references in a dictionary with an incremented ID. If the reference exists, I
//return its ID. Otherwise, I increment the ID and add the reference to the dictionary.
var id = 0;
if (objectDic.ContainsKey(value))
{
id = objectDic[value];
}
else
{
objectDic[value] = maxId++;
}
return id.ToString();
}
//Called during serialization
public bool IsReferenced(object context, object value)
{
//Return whether or not value exists in a reference bank.
//If false, the JSON will return as a full JSON object with "$id": "x"
//If true, the JSON will return "$ref": "x"
return objectDic.ContainsKey(value);
}
//Called during deserialization
public void AddReference(object context, string reference, object value)
{
//This method is called after the deserializer has created a new instance of the
//object. At this time, it's only the initial instance and no properties have been set.
//This method presents a problem because it does not allow you to create the instance or
//retrieve it from a repo and then return it for later use by the reference resolver.
//Therefore, I have to find the existing object by $id, remove it, and then add the new
//object created by the deseralizer. This creates the possibility for two instances of
//the same data object to exist within the running application, so, unfortunately, this
//will not work.
var e = objectDic.First(x => x.Value.ToString() == reference).Key;
objectDic.Remove(e);
objectDic[value] = reference.ParseInt().Value;
}
//Called during deserialization
public object ResolveReference(object context, string reference)
{
//This method retrieves an existing reference by $id and returns it.
var value = objectDic.FirstOrDefault(x => x.Value.ToString() == reference).Key;
return value;
}
}
推荐答案
As per others have recommended, you need a custom IReferenceResolver
:
class PersonNameAsIdResolver : IReferenceResolver
{
public void AddReference(object context, string reference, object value)
{
// This method is called during deserialize for $id
}
public string GetReference(object context, object value)
{
// Returns person name as value of $id
return ((Person)value).Name;
}
public bool IsReferenced(object context, object value)
{
// Returns false, so that $id is used, not $ref.
return false;
}
public object ResolveReference(object context, string reference)
{
// This method is called during deserialize for $ref
return null;
}
}
要使用它:
var settings = new Newtonsoft.Json.JsonSerializerSettings
{
PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects,
Formatting = Newtonsoft.Json.Formatting.Indented
};
settings.ReferenceResolverProvider = ()=> new PersonNameAsIdResolver();
更新
OP更新
AddReference
,因此替换该对象为时已晚.为了能够找到并填充所需的对象,您需要一个JsonConverter
,它在引用解析器之前被调用:
AddReference
is called while an object is being populated, so it has been too late to replace the object. To be able to find and populate desired object, you need a JsonConverter
, which is called before the reference resolver:
class PersonJsonConverter : JsonConverter
{
private readonly PersonNameAsIdResolver _idResolver;
public PersonJsonConverter(PersonNameAsIdResolver idResolver)
{
_idResolver = idResolver;
}
public override bool CanConvert(Type objectType)
=> objectType == typeof(Person);
// Can't write. There's nothing to changing for writing scenario.
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
var token = JToken.ReadFrom(reader);
if (token.Type == JTokenType.Null)
{
return null;
}
var obj = (JObject)token;
// The code below calls the resolver to find the existing instance.
// This can stop JSON.NET creating a new instance.
Person instance = null;
var @id = obj["$id"].Value<string>();
if (@id != null)
{
instance = (Person)_idResolver.ResolveReference(this, @id);
}
else
{
var @ref = obj["$ref"]?.Value<string>();
if (@ref != null)
{
instance = (Person)_idResolver.ResolveReference(this, @ref);
}
}
// Assuming can't resolve, create a new instance.
if (instance == null)
{
instance = new Person();
}
// This will populate existing Person object if found
serializer.Populate(obj.CreateReader(), instance);
return instance;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotSupportedException();
}
}
默认的序列化设置应如下所示:
And the default serialization settings should look like:
var settings = new Newtonsoft.Json.JsonSerializerSettings
{
PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects,
Formatting = Newtonsoft.Json.Formatting.Indented
};
var idResolver = new PersonNameAsIdResolver();
settings.Converters.Add(new PersonJsonConverter(idResolver));
settings.ReferenceResolverProvider = () => idResolver;
这篇关于控制JSON .NET的参考ID生成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!