将对象序列化为平面的Dictionary<string,string>

3

假设有一个类

public class MyClass
{
    public MyInnerClass Property1 {get;set;}
    public MyInnerClass2 Field1
    public MyInnnerClass[] Property2 {get;set;}
}

我需要一个序列化器,类似于MVC序列化器,将上述内容序列化成以下字典。
new {
        {
            "Property1.InnerField", "1",
        },
        {
            "Field1.InnerProperty", "7/7/2007"
        },
        {
            "Property2[0].InnerProperty", "0"
        },
        {
            "Property2[1].InnerProperty", "1"
        }
}

基本上它需要位置和原生值,或者进一步递归,直到找到非引用类型。
1个回答

3
谈到你的问题,我们可以用教学的方式回答,并排除任何现有序列化架构。这样一来,您将完全自由地进入对象引用树,不需要使用反射就能填充结果字典。
我们需要一个简单的方法来访问给定的对象,然后对于每个公共属性递归调用它本身。签名应包括一个字符串,表示当前父链(例如:"Property1.InnerProperty4.InnerProperty7"),返回类型应为一个Dictionary<string,string>,只包含当前和嵌套对象中的内容。 在开始时,它应该检查参数是值类型还是非值类型,在前者的情况下,只需创建一个新字典,添加一个新的键值对(父+名称,值)并返回;而在后者的情况下,创建一个新字典,然后对于每个公共属性,调用它本身,传递当前属性,父+名称字符串,将返回的字典与之前创建的字典连接起来,在循环结束时返回这个大字典。我们可以在开头添加另一个条件,检查传递的对象是否实现了类似IEnumerable接口的东西。在这种情况下,不仅要遍历它的成员,还要遍历它的索引器,并为每个项调用该方法。
我抽出时间来实现了昨天描述的内容。这里有一个递归方法,旨在返回给定对象的(公共属性全名)-(值)对的字典。当然,它可能不完全符合您想要实现的细节,但您将发现很多技巧、思路和概念来组成自己的方法:
namespace MyNamespace
{
    public class ClassA {
        int p1 = 1;
        string p2 = "abcdef"; 
        List<string> p3 = new List<string>() { "ghi","lmn" };
        ClassB p4 = new ClassB();
        ClassB p5 = null;

        public int PA1 { get { return p1; } }
        public string PA2 { get { return p2; } }
        public List<string> PA3 { get { return p3; } }
        public ClassB PA4 { get { return p4; } }
        public ClassB PA5 { get { return p5; } }
    }

    public class ClassB{
        private string p1 = "zeta";
        public string PB1 { get { return p1; } }
    }

    public class Program {

        public void Main()
        {
            ClassA o = new ClassA();
            Dictionary<string, string> result = GetPropertiesDeepRecursive(o, "[o]", new List<string>() { "MyNamespace" });
        }

        /// <summary>
        /// Returns a dictionary of propertyFullname-value pairs of the given object (and deep recursively for its public properties)
        /// note: it's object oriented (on purpose) and NOT type oriented! so it will just return values of not null object trees
        /// <param name="includedNamespaces">a list of full namespaces for whose types you want to deep search in the tree</param>
        /// </summary>        
        public Dictionary<string, string> GetPropertiesDeepRecursive(object o, string memberChain, List<string> includedNamespaces)
        {

            List<string> types_to_exclude_by_design = new List<string>() { "System.string", "System.String" };

            //the results bag
            Dictionary<string, string> r = new Dictionary<string, string>();

            //if o is null just return value = [null]
            if (o == null)
            {
                r.Add(memberChain, "[null]");
                return r;
            }

            //the current object argument type
            Type type = o.GetType();

            //reserve a special treatment for specific types by design (like string -that's a list of chars and you don't want to iterate on its items)
            if (types_to_exclude_by_design.Contains(type.FullName))
            {
                r.Add(memberChain, o.ToString());
                return r;
            }

            //if the type implements the IEnumerable interface...
            bool isEnumerable =
                type
                .GetInterfaces()
                .Any(t => t == typeof(System.Collections.IEnumerable));
            if (isEnumerable)
            {
                int i_item = 0;
                //loop through the collection using the enumerator strategy and collect all items in the result bag
                //note: if the collection is empty it will not return anything about its existence,
                //      because the method is supposed to catch value items not the list itself                
                foreach (object item in (System.Collections.IEnumerable)o)
                {
                    string itemInnerMember = string.Format("{0}[{1}]", memberChain, i_item++);
                    r = r.Concat(GetPropertiesDeepRecursive(item, itemInnerMember, includedNamespaces)).ToDictionary(e => e.Key, e => e.Value);
                }
                return r;
            }

            //here you need a strategy to exclude types you don't want to inspect deeper like int,string and so on
            //in those cases the method will just return the value using the specific object.ToString() implementation
            //now we are using a condition to include some specific types on deeper inspection and exclude all the rest
            if (!includedNamespaces.Contains(type.Namespace))
            {
                r.Add(memberChain, o.ToString());
                return r;
            }

            //otherwise go deeper in the object tree...            
            //and foreach object public property collect each value
            PropertyInfo[] pList = type.GetProperties();
            foreach (PropertyInfo p in pList)
            {
                object innerObject = p.GetValue(o, null);
                string innerMember = string.Format("{0}.{1}", memberChain, p.Name);
                r = r.Concat(GetPropertiesDeepRecursive(innerObject, innerMember, includedNamespaces)).ToDictionary(e => e.Key, e => e.Value);
            }
            return r;
        }
    }
}

迭戈,没关系。我以前做过这个,但是我丢失了那段代码。在MVC库中,一定有人已经实现了这个功能。如果可能的话,我想要直接获取并重用它。所以,我只是想问一下是否已经有解决方案在互联网上被写好了。我可以重新实现它,没有问题,但时间有点紧。 - Alwyn
哦,伙计,你真的不该这样。我本来期望有人指引我使用一个MVC类库而不是从头开始构建。但这远远超出了我的期望——非常感激。 - Alwyn

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