【问题标题】:C# JsonConvert SerializeXmlNode empty with attributesC# JsonConvert SerializeXmlNode 为空,带有属性
【发布时间】:2017-12-28 14:34:24
【问题描述】:

我正在使用 JsonConvert SerializeXmlNode 将 xml 转换为 json。我面临的问题是我有一个标签,它有时可能有价值,有时是空的

<AustrittDatum>2018-01-31+01:00</AustrittDatum>
...
<AustrittDatum xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>

结果 - 我在尝试将 json 反序列化为具有字符串属性“AustrittDatum”的 C# 对象时遇到异常 - “Newtonsoft.Json.JsonReaderException:'错误读取字符串。意外令牌:StartObject。路径'AustrittDatum'。' ", 因为

<AustrittDatum xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance xsi:nil="true"/> 

被序列化为

"AustrittDatum": {
  "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
  "@xsi:nil": "true"
},

我怎样才能强制它成为这样的东西 "AustrittDatum": "" 或者也许有一些适当的方法来解决它?

【问题讨论】:

    标签: c# xml json.net


    【解决方案1】:

    似乎当遇到带有xsi:nil="true" 的XML 元素时,Json.NET 的XmlNodeConverter 会创建一个带有您看到的属性的JSON 对象,而不是null JToken。这与 Newtonsoft 文档页面Converting between JSON and XML 一致:

    转化规则

    • 元素保持不变。
    • 属性以 @ 为前缀,并应位于对象的开头。
    • 单个子文本节点是直接针对元素的值,否则它们将通过#text 访问。
    • XML 声明和处理指令以 ? 为前缀。
    • 字符数据、cmets、空白和重要空白节点分别通过#cdata-section、#comment、#whitespace 和#significant-whitespace 访问。
    • 同一层级的多个同名节点组合成一个数组。
    • 空元素为空。

    如果从 JSON 创建的 XML 与您想要的不匹配,那么您将需要手动转换它...

    尽管如此,认为带有xsi:nil="true" 的元素将被转换为null JSON 值是合理的,因为xsi:nilpredefined w3c attribute。可能 Newtonsoft 没有这样做,因为这样的元素可以携带额外的属性,如果将元素转换为 null,这些属性将会丢失。

    您可以根据需要为XmlNodeConverter 提交enhancement request,但与此同时,以下扩展方法将对JToken 层次结构进行后处理,并将以前为nil 元素的对象转换为空JSON 值:

    public static class JTokenExtensions
    {
        const string XsiNamespace = @"http://www.w3.org/2001/XMLSchema-instance";
        readonly static string XmlNullValue = System.Xml.XmlConvert.ToString(true);
    
        public static JToken ReplaceXmlNilObjectsWithNull(this JToken root)
        {
            return root.ReplaceXmlNilObjects(t => JValue.CreateNull());
        }
    
        public static JToken ReplaceXmlNilObjects(this JToken root, Func<JToken, JToken> getReplacement)
        {
            var query = from obj in root.DescendantsAndSelf().OfType<JObject>()
                        where obj.Properties().Any(p => p.IsNilXmlTrueProperty())
                        select obj;
            foreach (var obj in query.ToList())
            {
                var replacement = getReplacement(obj);
                if (obj == root)
                    root = replacement;
                if (obj.Parent != null)
                    obj.Replace(replacement);
            }
            return root;
        }
    
        static IEnumerable<JToken> DescendantsAndSelf(this JToken node)
        {
            // Small wrapper adding this method to all JToken types.
            if (node == null)
                return Enumerable.Empty<JToken>();
            var container = node as JContainer;
            if (container != null)
                return container.DescendantsAndSelf();
            else
                return new[] { node };
        }
    
        static string GetXmlNamespace(this JProperty prop)
        {
            if (!prop.Name.StartsWith("@"))
                return null;
            var index = prop.Name.IndexOf(":");
            if (index < 0 || prop.Name.IndexOf(":", index+1) >= 0)
                return null;
            var ns = prop.Name.Substring(1, index - 1);
            if (string.IsNullOrEmpty(ns))
                return null;
            var nsPropertyName = "@xmlns:" + ns;
            foreach (var obj in prop.AncestorsAndSelf().OfType<JObject>())
            {
                var nsProperty = obj[nsPropertyName];
                if (nsProperty != null && nsProperty.Type == JTokenType.String)
                    return (string)nsProperty;
            }
            return null;
        }
    
        static bool IsNilXmlTrueProperty(this JProperty prop)
        {
            if (prop == null)
                return false;
            if (!(prop.Value.Type == JTokenType.String && (string)prop.Value == "true"))
                return false;
            if (!(prop.Name.StartsWith("@") && prop.Name.EndsWith(":nil")))
                return false;
            var ns = prop.GetXmlNamespace();
            return ns == XsiNamespace;
        }
    }
    

    然后像这样使用它:

    // Parse XML to XDocument
    var xDoc = XDocument.Parse(xmlString);
    
    // Convert the XDocument to an intermediate JToken hierarchy.
    var converter = new Newtonsoft.Json.Converters.XmlNodeConverter { OmitRootObject = true };
    var rootToken = JObject.FromObject(xDoc, JsonSerializer.CreateDefault(new JsonSerializerSettings { Converters = { converter } } ))
        // And replace xsi:nil objects will null JSON values
        .ReplaceXmlNilObjectsWithNull();
    
    // Deserialize to the final RootObject.
    var rootObject = rootToken.ToObject<RootObject>();
    

    生成:

    "AustrittDatum": [
      "2018-01-31+01:00",
      null
    ],
    

    这里我最初解析为XDocument,但您也可以使用旧的XmlDocument

    样本工作.Net fiddle

    【讨论】:

    • 感谢@dbc 的回答和调查。我很惊讶它不支持开箱即用。您的解决方案正在运行并且目标已达到,但在我的情况下,操作太难了,所以我将删除标有“xsi:nil”属性的节点,因为它们将被解析为字符串属性并以空值形式启动。但我认为我可以接受你的回答是正确的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多