控制JSON .NET的参考ID生成 [英] Controlling JSON .NET's reference ID generation

查看:55
本文介绍了控制JSON .NET的参考ID生成的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望能够控制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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆