【问题标题】:In JSON.NET how to get a reference to every deserialized object?在 JSON.NET 中,如何获取对每个反序列化对象的引用?
【发布时间】:2016-09-03 06:27:45
【问题描述】:

我正在尝试使用 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);
            }
        }
    }

【问题讨论】:

  • 我想你是想traverse 图表并填充一个列表,以便图表中的每个节点也被列表中的一个项目引用,对吧?
  • 您尝试过基于反射的方法吗?根据我的经验,它不是很慢。
  • 雷南:是的,没错
  • 那么请看this article。您必须访问图中的每个对象一次,然后在列表中保留对它的引用。这是最有效的方法之一。请不要在这样的任务中使用反射,因为它不仅效率不高,而且可能会为不良做法打开大门,这些做法以后会再次困扰您。
  • 任何代码/数据来显示您的问题,以便我们可以加快速度?

标签: c# json json.net


【解决方案1】:

您可以创建一个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 以获得最佳性能。

更新

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

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

  2. 抛出异常,指示结构不支持IDeserializationCallback

【讨论】:

  • 太棒了,谢谢!看起来它应该更干净。直到下周我才能再次玩它,但这看起来很棒!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多