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

5

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

[
  {
    "$id": "1",
    "Name": "James",
    "BirthDate": "1983-03-08T00:00Z",
    "LastModified": "2012-03-21T05:40Z"
  },
  {
    "$ref": "1"
  }
]

从$ref可以看出,这个JSON数组包含了同一个人(James)两次。第二次是对第一次的引用。

我想知道是否有一种方法将这个JSON反序列化为一个包含两个James人的对象。

目前,我正在使用以下代码:

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

var deserializedPersons = JsonConvert.DeserializeObject<List<Person>>(json, jsonSerializerSettings);

但是这只会给我一个包含相同Person实例的数组,重复两次:
object.ReferenceEquals(deserializedPersons[0], deserializedPersons[1]) // Evaluates to true

我发现了一个解决方法,但我对它不满意。它是将JSON字符串反序列化,然后使用上面的 jsonSerializerSettings 进行序列化,这样会在JSON中复制该人,并再次进行反序列化。这会导致我们使用大对象时出现严重的减速现象。
注意:我知道我可以更改从中检索此JSON的API以复制数据,但保留引用在通过网络发送响应JSON时节省了显著的空间。

如果“减速”是真正的问题,只需使用JSONFAST,它比James Newton-King的实现快两倍。 - Jeremy Thompson
1
遍历你的人员列表后,对每个反序列化后的人进行克隆是否适合? - recursive
啊,我看错了 - 我以为他试图摆脱引用。 - Ňɏssa Pøngjǣrdenlarp
1
@recursive 如果使用自定义引用解析器,则无需这样做。 - DavidG
2个回答

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;
    }
}

现在你可以像这样反序列化:
var jsonSerializerSettings = new JsonSerializerSettings()
{
    ReferenceResolver = new PersonReferenceResolver()
};

var deserializedPersons = JsonConvert.DeserializeObject<List<Person>>(
    json, jsonSerializerSettings);

编辑:我感到无聊,所以我制作了一个通用版本:

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;
    }
}

稍微有些新的用法:

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);

我现在在手机上,但今晚我会评估这个!看起来应该可以工作。 - Tgeo

0

我在$ref反序列化方面遇到了问题。 PreserveReferencesHandling只有在请求/响应开头处有元数据时才有用。 否则,我建议使用以下方法:

var settings = new JsonSerializerSettings();
settings.PreserveReferencesHandling = PreserveReferencesHandling.Objects; 
settings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore; //ign
var deserializedObjects = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(requestResult.Content, settings);

关于https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_MetadataPropertyHandling.htm的问题。

Default 0   Read metadata properties located at the start of a JSON object.
ReadAhead   1   Read metadata properties located anywhere in a JSON object. Note that this setting will impact performance.
Ignore  2   Do not try to read metadata properties.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接