如何将Newtonsoft Json.NET引用反序列化为单独的单个实例

Tge*_*geo 5 c# serialization json json.net

我有一块看起来像这样的JSON:

[
  {
    "$id": "1",
    "Name": "James",
    "BirthDate": "1983-03-08T00:00Z",
    "LastModified": "2012-03-21T05:40Z"
  },
  {
    "$ref": "1"
  }
]
Run Code Online (Sandbox Code Playgroud)

正如你可以通过$ ref告诉的那样,这个JSON数组包含两次相同的Person(James).第二次是对第一次的引用.

我想知道是否有办法这个JSON 反序列化为一个包含两个詹姆斯人副本的对象.

目前,我正在使用这个:

var jsonSerializerSettings = new JsonSerializerSettings()
{
     PreserveReferencesHandling = PreserveReferencesHandling.None,
     ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};

var deserializedPersons = JsonConvert.DeserializeObject<List<Person>>(json, jsonSerializerSettings);
Run Code Online (Sandbox Code Playgroud)

但这只是给我一个数组,其中包含相同的Person实例,两次:

object.ReferenceEquals(deserializedPersons[0], deserializedPersons[1]) // Evaluates to true
Run Code Online (Sandbox Code Playgroud)

我找到了一个我不满意的解决方法,它只是反序列化JSON字符串,然后使用上面的jsonSerializerSettings序列化它,它将复制JSON中的人,然后再次反序列化它.这导致我们正在使用的大型物体出现严重减速.

注意:我知道我可以更改从检索此JSON的API以复制数据,但保留引用可以在通过线路发送响应JSON时节省大量空间.

Dav*_*idG 5

您可以使用自定义参考解析器。例如,假设Name是您的对象的“主键”,这应该可行。当然,您可能想让它更通用。

public class PersonReferenceResolver : IReferenceResolver
{
    private readonly IDictionary<string, Person> _objects = 
        new Dictionary<string, Person>();

    public object ResolveReference(object context, string reference)
    {
        Person p;
        if (_objects.TryGetValue(reference, out p))
        {
            //This is the "clever" bit. Instead of returning the found object
            //we just return a copy of it.
            //May be better to clone your class here...
            return new Person
            {
                Name = p.Name,
                BirthDate = p.BirthDate,
                LastModified = p.LastModified
            };
        }

        return null;
    }

    public string GetReference(object context, object value)
    {
        Person p = (Person)value;
        _objects[p.Name] = p;

        return p.Name;
    }

    public bool IsReferenced(object context, object value)
    {
        Person p = (Person)value;

        return _objects.ContainsKey(p.Name);
    }

    public void AddReference(object context, string reference, object value)
    {
        _objects[reference] = (Person)value;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你像这样反序列化:

var jsonSerializerSettings = new JsonSerializerSettings()
{
    ReferenceResolver = new PersonReferenceResolver()
};

var deserializedPersons = JsonConvert.DeserializeObject<List<Person>>(
    json, jsonSerializerSettings);
Run Code Online (Sandbox Code Playgroud)

编辑:我很无聊,所以我做了一个通用版本:

public class GenericResolver<TEntity> : IReferenceResolver
    where TEntity : ICloneable, new()
{
    private readonly IDictionary<string, TEntity> _objects = new Dictionary<string, TEntity>();
    private readonly Func<TEntity, string> _keyReader;

    public GenericResolver(Func<TEntity, string> keyReader)
    {
        _keyReader = keyReader;
    }

    public object ResolveReference(object context, string reference)
    {
        TEntity o;
        if (_objects.TryGetValue(reference, out o))
        {
            return o.Clone();
        }

        return null;
    }

    public string GetReference(object context, object value)
    {
        var o = (TEntity)value;
        var key = _keyReader(o);
        _objects[key] = o;

        return key;
    }

    public bool IsReferenced(object context, object value)
    {
        var o = (TEntity)value;
        return _objects.ContainsKey(_keyReader(o));
    }

    public void AddReference(object context, string reference, object value)
    {
        if(value is TEntity)
            _objects[reference] = (TEntity)value;
    }
}
Run Code Online (Sandbox Code Playgroud)

稍微有点新的用法:

var jsonSerializerSettings = new JsonSerializerSettings()
{
    //Now we need to specify the type and how to get the object's key
    ReferenceResolver = new GenericResolver<Person>(p => p.Name)
};

var deserializedPersons = JsonConvert.DeserializeObject<List<Person>>(
    json, jsonSerializerSettings);
Run Code Online (Sandbox Code Playgroud)