如何使Json.NET序列化和反序列化也实现IDictionary< string,object>的自定义动态类型的声明属性? [英] How can I make Json.NET serialize and deserialize declared properties of custom dynamic types that also implement IDictionary<string, object>?

查看:85
本文介绍了如何使Json.NET序列化和反序列化也实现IDictionary< string,object>的自定义动态类型的声明属性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个从 DynamicObject 类型派生的自定义类型.此类型具有在该类型中声明的固定属性.因此,它允许用户除了提供所需的任何动态属性外,还提供一些必需的属性.当我使用 JsonConvert.DeserializeObject< MyType>(json)方法反序列化此类型的数据时,它不会设置声明的属性,但是可以通过动态对象上的对象索引器属性访问这些属性.目的.这告诉我,它只是将对象视为字典,并且不会尝试调用已声明的属性设置器,也不会使用它们来推断属性类型信息.

I have a custom type derived from the DynamicObject type. This type has fixed properties declared in the type. So it allows the user to provide some required properties in addition to any dynamic properties they want. When I use the JsonConvert.DeserializeObject<MyType>(json) method to deserialize the data for this type, it does not set the declared properties, but those properties are accessible via the object indexer property on the dynamic object. This tells me that it simply treats the object as a dictionary and does not try to call the declared property setters nor is it using them for inferring the property type information.

以前有人遇到过这种情况吗?知道如何在反序列化对象数据时指示 JsonConvert 类将声明的属性考虑在内吗?

Has anyone encountered this situation before? Any idea how I can instruct the JsonConvert class to take the declared properties into account when deserializing the object data?

我尝试使用自定义的 JsonConverter ,但这需要我编写复杂的JSON读取和写入方法.我希望找到一种方法,通过覆盖 JsonContractResolver JsonConverter

I tried to use a custom JsonConverter, but that requires me to write the complex JSON read and writ methods. I was hoping to find a way inject property contract information by overriding the JsonContractResolver or JsonConverter, etc.


//#define IMPLEMENT_IDICTIONARY

using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using Newtonsoft.Json;

namespace ConsoleApp1
{
    class Program
    {
        public class MyDynamicObject : DynamicObject
#if IMPLEMENT_IDICTIONARY
            , IDictionary<string, object>
#endif
        {
            private Dictionary<string, object> m_Members;

            public MyDynamicObject()
            {
                this.m_Members = new Dictionary<string, object>();
            }


#if IMPLEMENT_IDICTIONARY
            public int Count { get { return this.m_Members.Count; } }

            public ICollection<string> Keys => this.m_Members.Keys;

            public ICollection<object> Values => this.m_Members.Values;

            bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;

            /// <summary>
            /// Gets or sets the specified member value.
            /// </summary>
            /// <param name="memberName">Name of the member in question.</param>
            /// <returns>A value for the specified member.</returns>
            public object this[string memberName]
            {
                get
                {
                    object value;
                    if (this.m_Members.TryGetValue(memberName, out value))
                        return value;
                    else
                        return null;
                }
                set => this.m_Members[memberName] = value;
            }
#endif


            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                this.m_Members.TryGetValue(binder.Name, out result);
                return true;
            }

            public override bool TrySetMember(SetMemberBinder binder, object value)
            {
                this.m_Members[binder.Name] = value;
                return true;
            }

            public override bool TryDeleteMember(DeleteMemberBinder binder)
            {
                return this.m_Members.Remove(binder.Name);
            }

            public override IEnumerable<string> GetDynamicMemberNames()
            {
                var names = base.GetDynamicMemberNames();
                return this.m_Members.Keys;
            }

#if IMPLEMENT_IDICTIONARY
            bool IDictionary<string, object>.ContainsKey(string memberName)
            {
                return this.m_Members.ContainsKey(memberName);
            }

            public void Add(string memberName, object value)
            {
                this.m_Members.Add(memberName, value);
            }

            public bool Remove(string memberName)
            {
                return this.m_Members.Remove(memberName);
            }

            public bool TryGetValue(string memberName, out object value)
            {
                return this.m_Members.TryGetValue(memberName, out value);
            }

            public void Clear()
            {
                this.m_Members.Clear();
            }

            void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> member)
            {
                ((IDictionary<string, object>)this.m_Members).Add(member);
            }

            bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> member)
            {
                return ((IDictionary<string, object>)this.m_Members).Contains(member);
            }

            public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
            {
                ((IDictionary<string, object>)this.m_Members).CopyTo(array, arrayIndex);
            }

            bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> member)
            {
                return ((IDictionary<string, object>)this.m_Members).Remove(member);
            }

            public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
            {
                return this.m_Members.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.m_Members.GetEnumerator();
            }
#endif
        }

        public class ProxyInfo
        {
            public string Server;
            public int Port;
        }

        public class CustomDynamicObject : MyDynamicObject
        {
            //[JsonProperty] // NOTE: Cannot do this.
            public string Name { get; set; }

            //[JsonProperty]  // NOTE: Cannot do this.
            public ProxyInfo Proxy { get; set; }
        }


        static void Main(string[] args)
        {
            dynamic obj = new CustomDynamicObject()
            {
                Name = "Test1",
                Proxy = new ProxyInfo() { Server = "http://test.com/",  Port = 10102 }
            };
            obj.Prop1 = "P1";
            obj.Prop2 = 320;

            string json = JsonConvert.SerializeObject(obj);  // Returns: { "Prop1":"P1", "Prop2":320 }

            // ISSUE #1: It did not serialize the declared properties. Only the dynamically added properties are serialized.
            //           Following JSON was expected. It produces correct JSON if I mark the declared properties with
            //           JsonProperty attribute, which I cannot do in all cases.
            string expectedJson = "{ \"Prop1\":\"P1\", \"Prop2\":320, \"Name\":\"Test1\", \"Proxy\":{ \"Server\":\"http://test.com/\", \"Port\":10102 } }";


            CustomDynamicObject deserializedObj = JsonConvert.DeserializeObject<CustomDynamicObject>(expectedJson);

            // ISSUE #2: Deserialization worked in this case, but does not work once I re-introduce the IDictionary interface on my base class.
            //           In that case, it does not populate the declared properties, but simply added all 4 properties to the underlying dictionary.
            //           Neither does it infer the ProxyInfo type when deserializing the Proxy property value and simply bound the JObject token to
            //           the dynamic object.
        }
    }
}

我希望它像常规类型一样使用反射来解析属性及其类型信息.但这似乎只是将对象视为常规词典.

I would have expected it to use reflection to resolve the property and its type information like it does for regular types. But it seems as if it simply treats the object as a regular dictionary.

请注意:

  • 我无法删除 IDictionary< string,object> 接口,因为我API中的某些用例依赖于该对象是字典,而不是动态的.

  • I cannot remove the IDictionary<string, object> interface since some of the use-cases in my API rely on the object to be a dictionary, not dynamic.

在要声明的所有要声明的属性中添加 [JsonProperty] 是不切实际的,因为它的派生类型是由其他开发人员创建的,并且它们不需要显式关心持久性机制.

Adding [JsonProperty] to all declared properties to be serialized is not practical because its derived types are created by other developers and they do not need to care about the persistence mechanism explicitly.

关于如何使其正常工作的任何建议?

Any suggestions on how I can make it work correctly?

推荐答案

您在这里遇到一些问题:

You have a few problems here:

  1. 您需要正确覆盖 DynamicObject.GetDynamicMemberNames() ,如此答案 通过://stackoverflow.com/users/4242590/albertk> AlbertK以便Json.NET能够序列化您的动态属性.

  1. You need to correctly override DynamicObject.GetDynamicMemberNames() as explained in this answer to Serialize instance of a class deriving from DynamicObject class by AlbertK for Json.NET to be able to serialize your dynamic properties.

(此问题已在您的问题的编辑版本中得到解决.)

(This has already been fixed in the edited version of your question.)

声明的属性不会显示,除非您用 [JsonProperty] 明确标记(如 C#如何在从DynamicObject继承的类上序列化(JSON,XML)常规属性 ),但您的类型定义是只读的,无法修改.

Declared properties do not show up unless you explicitly mark them with [JsonProperty] (as explained in this answer to C# How to serialize (JSON, XML) normal properties on a class that inherits from DynamicObject) but your type definitions are read-only and cannot be modified.

这里的问题似乎是

The problem here seems to be that JsonSerializerInternalWriter.SerializeDynamic() only serializes declared properties for which JsonProperty.HasMemberAttribute == true. (I don't know why this check is made there, it would seem to make more sense to set CanRead or Ignored inside the contract resolver.)

您希望您的类实现 IDictionary< string,object> ,但是如果这样做,则会破坏反序列化;声明的属性不再填充,而是添加到字典中.

You would like for your class to implement IDictionary<string, object>, but if you do, it breaks deserialization; declared properties are no longer populated, but are instead added to the dictionary.

这里的问题似乎是 IDictionary< TKey时,nofollow noreferrer> DefaultContractResolver.CreateContract() 返回 JsonDictionaryContract 而不是 JsonDynamicContract ,TValue> 表示任何 TKey TValue .

The problem here seems to be that DefaultContractResolver.CreateContract() returns JsonDictionaryContract rather than JsonDynamicContract when the incoming type implements IDictionary<TKey, TValue> for any TKey and TValue.

假设您已解决问题1,可以使用自定义合同解析器,例如:

Assuming you have fixed issue #1, issues #2 and #3 can be handled by using a custom contract resolver such as the following:

public class MyContractResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        // Prefer JsonDynamicContract for MyDynamicObject
        if (typeof(MyDynamicObject).IsAssignableFrom(objectType))
        {
            return CreateDynamicContract(objectType);
        }
        return base.CreateContract(objectType);
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);
        // If object type is a subclass of MyDynamicObject and the property is declared
        // in a subclass of MyDynamicObject, assume it is marked with JsonProperty 
        // (unless it is explicitly ignored).  By checking IsSubclassOf we ensure that 
        // "bookkeeping" properties like Count, Keys and Values are not serialized.
        if (type.IsSubclassOf(typeof(MyDynamicObject)) && memberSerialization == MemberSerialization.OptOut)
        {
            foreach (var property in properties)
            {
                if (!property.Ignored && property.DeclaringType.IsSubclassOf(typeof(MyDynamicObject)))
                {
                    property.HasMemberAttribute = true;
                }
            }
        }
        return properties;
    }
}

然后,要使用合同解析器,将其缓存以提高性能:

Then, to use the contract resolver, cache it somewhere for performance:

static IContractResolver resolver = new MyContractResolver();

然后执行:

var settings = new JsonSerializerSettings
{
    ContractResolver = resolver,
};
string json = JsonConvert.SerializeObject(obj, settings);

样本小提琴此处.

这篇关于如何使Json.NET序列化和反序列化也实现IDictionary&lt; string,object&gt;的自定义动态类型的声明属性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆