在JSON.NET中如何获取对每个反序列化对象的引用?

时间:2016-09-02 20:07:42

标签: c# json json.net

我正在尝试使用JSON.NET实现IDeserializationCallback。我正在反序列化一个对象,我想生成一个列表,列出所有反序列化的实现IDeserializationCallback的对象,最好的方法是什么? JSON.NET是否有任何适当的扩展点来促进这一点?我有一个(看似)工作解决方案,但是它非常难看,所以我确信必须有更好的方法来做到这一点。感谢任何帮助,谢谢!

    private static JsonSerializer serializer = new JsonSerializer();

    static cctor()
    {
        serializer.Converters.Add(new DeserializationCallbackConverter());
    }

    public static T Deserialize<T>(byte[] data)
    {
        using (var reader = new JsonTextReader(new StreamReader(new MemoryStream(data))))
        using (DeserializationCallbackConverter.NewDeserializationCallbackBlock(reader))
            return serializer.Deserialize<T>(reader);
    }

    private class DeserializationCallbackConverter : JsonConverter
    {
        [ThreadStatic]
        private static ScopedConverter currentConverter;

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            return currentConverter.ReadJson(reader, objectType, serializer);
        }

        public override bool CanConvert(Type objectType)
        {
            return currentConverter == null ? false : currentConverter.CanConvert();
        }

        public override bool CanWrite
        {
            get { return false; }
        }

        public static IDisposable NewDeserializationCallbackBlock(JsonReader reader)
        {
            return new ScopedConverter(reader);
        }

        private class ScopedConverter : IDisposable
        {
            private JsonReader jsonReader;
            private string currentPath;
            private List<IDeserializationCallback> callbackObjects;

            public ScopedConverter(JsonReader reader)
            {
                jsonReader = reader;
                callbackObjects = new List<IDeserializationCallback>();
                currentConverter = this;
            }

            public object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer)
            {
                var lastPath = currentPath;
                currentPath = reader.Path;
                var obj = serializer.Deserialize(reader, objectType);
                currentPath = lastPath;

                var dc = obj as IDeserializationCallback;
                if (dc != null && callbackObjects != null)
                    callbackObjects.Add(dc);
                return obj;
            }

            public bool CanConvert()
            {
                return jsonReader.Path != currentPath;
            }

            public void Dispose()
            {
                currentConverter = null;
                foreach (var obj in callbackObjects)
                    obj.OnDeserialization(null);
            }
        }
    }

1 个答案:

答案 0 :(得分:3)

您可以创建一个custom contract resolver,添加额外的人工OnDeserialized回调来跟踪参考类型对象的创建。这是一个例子:

public interface IObjectCreationTracker
{
    void Add(object obj);

    ICollection<object> CreatedObjects { get; }
}

public class ReferenceObjectCreationTracker : IObjectCreationTracker
{
    public ReferenceObjectCreationTracker()
    {
        this.CreatedObjects = new HashSet<object>();
    }

    public void Add(object obj)
    {
        if (obj == null)
            return;
        var type = obj.GetType();
        if (type.IsValueType || type == typeof(string))
            return;
        CreatedObjects.Add(obj);
    }

    public ICollection<object> CreatedObjects { get; private set; }
}

public class ObjectCreationTrackerContractResolver : DefaultContractResolver
{
    readonly SerializationCallback callback = (o, context) =>
        {
            var tracker = context.Context as IObjectCreationTracker;
            if (tracker != null)
                tracker.Add(o);
        };

    protected override JsonContract CreateContract(Type objectType)
    {
        var contract = base.CreateContract(objectType);
        contract.OnDeserializedCallbacks.Add(callback);
        return contract;
    }
}

然后按如下方式使用它:

public static class JsonExtensions
{
    public static T DeserializeWithTracking<T>(string json, out ICollection<object> objects)
    {
        var tracker = new ReferenceObjectCreationTracker();
        var settings = new JsonSerializerSettings
        {
            ContractResolver = new ObjectCreationTrackerContractResolver(),
            Context = new StreamingContext(StreamingContextStates.All, tracker),
            // Add other settings as required.  
            TypeNameHandling = TypeNameHandling.Auto, 
        };
        var obj = (T)JsonConvert.DeserializeObject<T>(json, settings);
        objects = tracker.CreatedObjects;
        return obj;
    }
}

请注意,这仅返回非字符串引用类型的实例。返回值类型的实例更成问题,因为没有明显的方法来区分最终通过属性设置器嵌入到较大对象中的值类型和作为boxed reference保留在对象图中的值类型,例如this question。如StreamingContext.Context所示。如果盒装值类型最终嵌入到某个较大的对象中,则无法保留对它的直接引用。

另请注意使用cache the contract resolver将跟踪器传递回回调。

您可能需要https://github.com/swisnl/jQuery-contextMenu#dependencies才能获得最佳效果。

<强>更新

在回答有关如何使用Json.NET实现IDeserializationCallback的更新问题时,上述内容应适用于引用类型。对于实现此接口的值类型,您可以:

  1. 立即在OnDeserialized回调中调用该方法,而不是将其推迟到序列化完成,或

  2. 抛出异常,表明结构不支持IDeserializationCallback