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

标签 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);
            }
        }
    }

最佳答案

您可以创建一个 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;
    }
}

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

还要注意 StreamingContext.Context 的使用将跟踪器向下传递到回调中。

您可能想要 cache the contract resolver以获得最佳性能。

更新

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

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

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

关于c# - 在 JSON.NET 中如何获取对每个反序列化对象的引用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39300005/

相关文章:

c# - 在 64 位中从 SysListView32 获取文本

json - 在Grails 2.4.0中将对象呈现为json时排除属性

c# - 使用 NewtonSoft Json.NET 从基类型集合序列化派生类型的属性

c# - 在两个 asp.net web 应用程序之间共享相同的 SessionId

c# - 我的所有验证都不起作用

json - 无法将字典序列化为 JSON

javascript - 在 JSON 表示法中使用变量

c# - 从结构化数据构建 JSON 层次结构

powershell - Powershell vs .NET 中的 Newtonsoft Json Linq 问题?

C# XAML 拉伸(stretch)网格以适合 listViewItem