3

我有一个深度嵌套的对象模型:

public class CheatSheet {
    public string Leader { get; set; }
    public List<Section> Sections { get; set; }
}

public class Section {
    public string Title { get; set; }
    public List<SubSection> SubSections { get; set; }
}

public class SubSection {
    public string Title { get; set; }
    public List<Cheat> Cheats { get; set; }
}

public class Cheat {
    public string Affected { get; set; }
    public string Text { get; set; }
    public string Hint { get; set; }
    public string Url { get; set; }
}

我已经将它序列化为 YAML,没有任何问题:

var serializer = new YamlDotNet.Serialization.Serializer();
var sb = new StringBuilder();
var sw = new StringWriter(sb);
serializer.Serialize(sw, model);
string yaml = sb.ToString();

yaml 看起来不错,非常类似于 JSON 或 HJSON 表示。

我现在想反序列化它 - nb 我想将它反序列化为动态对象而不是原始模型(仅在此示例中首先用于生成 YAML,它不会存在于最终程序集中) .

var sr = new StringReader(yaml);
var deserializer = new YamlDotNet.Serialization.Deserializer();
dynamic expando = deserializer.Deserialize<ExpandoObject>(sr);

问题是生成的 expando 很难使用,包含许多不必要的嵌套级别。例如:

expando.Sections[0]["Title"]
expando.Sections[0]["SubSections"][0]["Title"]
expando.Sections[0]["SubSections"][0]["Cheats"][0]["Text"]

但我希望这是

expando.Sections[0].Title
expando.Sections[0].SubSections[0].Title
expando.Sections[0].SubSections[0].Cheats[0].Text

这有可能吗?

在项目 Gitcheatsheet.TestHarness 中的https://github.com/PhilipDaniels/Lithogen提供了一个重现程序 ,提交 2db9a0491e8ab50bb07aee552ddec6697c4b8bfc

4

3 回答 3

4

好吧,我在一定程度上回答了我自己的问题。这门课会做我要求的文件(未在其他人身上测试过)。每个 YAML 字符串易于扩展为多个文档。可以通过尝试转换为 等来改进标量的double处理DateTime

当然有更好的方法可以做到这一点,但是这个项目的 API 非常混乱。

public static class YamlUtils
{
    /// <summary>
    /// Converts a YAML string to an <code>ExpandoObject</code>.
    /// </summary>
    /// <param name="yaml">The YAML string to convert.</param>
    /// <returns>Converted object.</returns>
    public static ExpandoObject ToExpando(string yaml)
    {
        using (var sr = new StringReader(yaml))
        {
            var stream = new YamlStream();
            stream.Load(sr);
            var firstDocument = stream.Documents[0].RootNode;
            dynamic exp = ToExpando(firstDocument);
            return exp;
        }
    }

    /// <summary>
    /// Converts a YAML node to an <code>ExpandoObject</code>.
    /// </summary>
    /// <param name="node">The node to convert.</param>
    /// <returns>Converted object.</returns>
    public static ExpandoObject ToExpando(YamlNode node)
    {
        ExpandoObject exp = new ExpandoObject();
        exp = (ExpandoObject)ToExpandoImpl(exp, node);
        return exp;
    }

    static object ToExpandoImpl(ExpandoObject exp, YamlNode node)
    {
        YamlScalarNode scalar = node as YamlScalarNode;
        YamlMappingNode mapping = node as YamlMappingNode;
        YamlSequenceNode sequence = node as YamlSequenceNode;

        if (scalar != null)
        {
            // TODO: Try converting to double, DateTime and return that.
            string val = scalar.Value;
            return val;
        }
        else if (mapping != null)
        {
            foreach (KeyValuePair<YamlNode, YamlNode> child in mapping.Children)
            {
                YamlScalarNode keyNode = (YamlScalarNode)child.Key;
                string keyName = keyNode.Value;
                object val = ToExpandoImpl(exp, child.Value);
                exp.SetProperty(keyName, val);
            }
        }
        else if (sequence != null)
        {
            var childNodes = new List<object>();
            foreach (YamlNode child in sequence.Children)
            {
                var childExp = new ExpandoObject();
                object childVal = ToExpandoImpl(childExp, child);
                childNodes.Add(childVal);
            }
            return childNodes;
        }

        return exp;
    }
}

其中 SetProperty 是一种扩展方法,由于某种原因我不记得了:

public static void SetProperty(this IDictionary<string, object> target, string name, object thing)
{
    target[name] = thing;
}

谨防!此代码尚未经过全面测试!可能存在一些边缘条件。

于 2015-02-25T20:18:15.017 回答
2

更符合该库的解决方案是DefaultContainersNodeTypeResolver用自定义解析器替换默认类型解析器,该解析器将映射解析为ExpandoObject而不是Dictionary<object, object>. 见下文:

using System;
using System.Collections.Generic;
using System.Dynamic;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;

public class ExpandoNodeTypeResolver : INodeTypeResolver
{
    public bool Resolve(NodeEvent nodeEvent, ref Type currentType)
    {
        if (currentType == typeof(object))
        {
            if (nodeEvent is SequenceStart)
            {
                currentType = typeof(List<object>);
                return true;
            }
            if (nodeEvent is MappingStart)
            {
                currentType = typeof(ExpandoObject);
                return true;
            }
        }

        return false;
    }
}

要使用它,请执行以下操作:

var deserializer = new DeserializerBuilder()
            .WithNodeTypeResolver(
                  new ExpandoNodeTypeResolver(), 
                  ls => ls.InsteadOf<DefaultContainersNodeTypeResolver>())
            .Build();
dynamic result = deserializer.Deserialize(input);
于 2018-04-25T08:43:58.200 回答
-1

当您说“(仅在此示例中首先用于生成 YAML,它不会存在于最终程序集中)”时,您实际上是在消除执行此操作的方式。根据您无法包含此原始模型的原因,如果您无法将其作为典型依赖项引用,我鼓励您考虑将其复制到最终程序集中。

具体来说:

如果你想访问

expando.Sections[0]["Title"]
expando.Sections[0]["SubSections"][0]["Title"]
expando.Sections[0]["SubSections"][0]["Cheats"][0]["Text"]

喜欢

expando.Sections[0].Title
expando.Sections[0].SubSections[0].Title
expando.Sections[0].SubSections[0].Cheats[0].Text

然后你需要用一个对象模型反序列化它,这样编译器才能理解你的数据结构并让你.Subsections[0]. 因此,如果您可以在反序列化此数据的程序集(“最终程序集”)中包含对象模型,那么您可以使用反序列化

var sr = new StringReader(yaml);
var deserializer = new YamlDotNet.Serialization.Deserializer();
Section[] sections = deserializer.Deserialize<Section[]>(sr);

和访问数据

sections[0].Title
sections[0].Subsections[0].Title

等等

于 2017-10-05T04:05:08.263 回答