c# - 控制 JSON .NET 的参考 ID 生成
问题描述
我希望能够控制 JSON .NET 如何生成其元引用 ID,例如"$id": "1"
. 采取以下代码:
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);
JSON forperson
如下所示:
{
"$id": "1",
"Name": "bob",
"Mother": {
"$id": "2",
"Name": "jane",
"Mother": null
}
}
但是,如果我person.Mother
直接序列化,JSON 看起来像这样:
{
"$id": "1",
"Name": "jane",
"Mother": null
}
在第一个 JSON 中,Jane 是"$id": "2"
,但直接序列化 Jane 是"$id": "1"
。这是我在正常情况下所期望的行为,因为序列化程序按照遍历对象的顺序分配 ID,但我真的很想覆盖 ID 生成,以便我可以将其设为对象引用本身的哈希。这样,无论是作为父级成员序列化还是单独序列化,Jane 每次都会为每个正在运行的程序实例生成相同的 ID。
更新
根据所选答案中的示例代码和评论中的推荐,我使用了IReferenceResolver
. 事实证明我不能使用它,但无论如何我都会包含下面的代码。这不起作用的原因是因为我试图将 JSON.NET 混为一个快速而肮脏的克隆工具,所以我不能因为它不适合我的需要而责备它。从那以后我又回到了我自己的自定义克隆实用程序上,所以我不再需要这个了。
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;
}
}
解决方案
根据其他人的建议,您需要一个自定义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
,它在引用解析器之前调用:
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();
}
}
默认序列化设置应如下所示:
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;
推荐阅读
- sql-server - 在 Windows 窗体中更新数据库值
- firebase-realtime-database - 将 iOS/android 项目连接到 firebase
- c# - C#找到int的最大值,然后输出最大值的变量名
- machine-learning - 如果数据具有线性关系,线性回归不会导致样本内误差为零
- php - PHP mysql-7.2 无法在 Ubuntu 上安装
- excel - 了解条件锁定
- html - 如何在 div 中定位 css 中的子元素?
- java - 为什么这段代码不起作用?(使用 Selenium-Java 登录谷歌)
- c++ - Add of std::valarray got different sizes with different operand orders
- javascript - 在 useState 中保存的数组中包含的一系列对象中增加键的值 (+1)