Json.NET:使用$ element将JSON转换为XML

时间:2016-03-10 18:55:28

标签: json.net

Json.NET似乎正在处理许多正确无效XML的名称,例如:

JsonConvert.DeserializeXmlNode("{"name!1": "test"}").OuterXml

结果:

<name_x0021_1>test</name_x0021_1>

但是尝试转换传递JSONLint的以下内容:

{"$": "test"}

结果

Result Message: System.Xml.XmlException : The ':' character, hexadecimal value 0x3A, cannot be included in a name.

此错误消息本身似乎令人困惑,因为它表明JSON中的一个名称具有:字符。可能有一个很好的理由,但有没有办法让它转换为XML而不会抛出异常,因为一些API似乎返回&#34; $&#34;:&#34; ....& #34;对

1 个答案:

答案 0 :(得分:0)

<强>更新

这已在提交9.0.1中的Json.NET b71ca75中修复。

原始答案

这可能是Json.NET XmlNodeConverter.ReadElement()中的错误。 Json.NET有几个保留的属性名称,所有这些都以$开头,显示为here

    public const string IdPropertyName = "$id";
    public const string RefPropertyName = "$ref";
    public const string TypePropertyName = "$type";
    public const string ValuePropertyName = "$value";
    public const string ArrayValuesPropertyName = "$values";

然后,在ReadElement()中,对以$开头的属性名称进行了特殊处理:

    private void ReadElement(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName, XmlNamespaceManager manager)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            throw JsonSerializationException.Create(reader, "XmlNodeConverter cannot convert JSON with an empty property name to XML.");
        }

        Dictionary<string, string> attributeNameValues = ReadAttributeElements(reader, manager);

        string elementPrefix = MiscellaneousUtils.GetPrefix(propertyName);

        if (propertyName.StartsWith('@'))
        {
            string attributeName = propertyName.Substring(1);
            string attributePrefix = MiscellaneousUtils.GetPrefix(attributeName);

            AddAttribute(reader, document, currentNode, attributeName, manager, attributePrefix);
        }
        else if (propertyName.StartsWith('$'))
        {
            if (propertyName == JsonTypeReflector.ArrayValuesPropertyName)
            {
                propertyName = propertyName.Substring(1);
                elementPrefix = manager.LookupPrefix(JsonNamespaceUri);
                CreateElement(reader, document, currentNode, propertyName, manager, elementPrefix, attributeNameValues);
            }
            else
            {
// Your code throws an exception going down this branch.
                string attributeName = propertyName.Substring(1);
                string attributePrefix = manager.LookupPrefix(JsonNamespaceUri);
                AddAttribute(reader, document, currentNode, attributeName, manager, attributePrefix);
            }
        }
        else
        {
            CreateElement(reader, document, currentNode, propertyName, manager, elementPrefix, attributeNameValues);
        }
    }

怀疑 propertyName.StartsWith('$')分支只应用于保留属性名称,这意味着该方法应如下所示:

    private void ReadElement(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName, XmlNamespaceManager manager)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            throw JsonSerializationException.Create(reader, "XmlNodeConverter cannot convert JSON with an empty property name to XML.");
        }

        Dictionary<string, string> attributeNameValues = ReadAttributeElements(reader, manager);

        if (propertyName.StartsWith('@'))
        {
            string attributeName = propertyName.Substring(1);
            string attributePrefix = MiscellaneousUtils.GetPrefix(attributeName);

            AddAttribute(reader, document, currentNode, attributeName, manager, attributePrefix);
        }
        else if (propertyName == JsonTypeReflector.ArrayValuesPropertyName)
        {
            propertyName = propertyName.Substring(1);
            var elementPrefix = manager.LookupPrefix(JsonNamespaceUri);
            CreateElement(reader, document, currentNode, propertyName, manager, elementPrefix, attributeNameValues);
        }
        else if (propertyName == JsonTypeReflector.IdPropertyName
            || propertyName == JsonTypeReflector.RefPropertyName
            || propertyName == JsonTypeReflector.TypePropertyName
            || propertyName == JsonTypeReflector.ValuePropertyName)
        {
            string attributeName = propertyName.Substring(1);
            string attributePrefix = manager.LookupPrefix(JsonNamespaceUri);
            AddAttribute(reader, document, currentNode, attributeName, manager, attributePrefix);
        }
        else
        {
            var elementPrefix = MiscellaneousUtils.GetPrefix(propertyName);
            CreateElement(reader, document, currentNode, propertyName, manager, elementPrefix, attributeNameValues);
        }
    }

(老实说,"$value"的处理看起来也很可疑,因为多态属性的值可能是任何JSON对象。)

您可能需要report an issue

与此同时,您可以将JSON加载到JToken,手动重新映射以$开头的名称,然后转换为XmlDocument,如下所示:

var token = JToken.Parse(json);

token.RenameReplaceProperties(s => (s.StartsWith("$") && !JsonExtensions.IsReserved(s) ? XmlConvert.EncodeName(s) : s));

var xml = token.ToXmlNode();

使用扩展方法:

public static class JsonExtensions
{
    const string IdPropertyName = "$id";
    const string RefPropertyName = "$ref";
    const string TypePropertyName = "$type";
    const string ValuePropertyName = "$value";
    const string ArrayValuesPropertyName = "$values";

    public static bool IsReserved(string s)
    {
        return s == IdPropertyName || s == RefPropertyName || s == TypePropertyName || s == ValuePropertyName || s == ArrayValuesPropertyName;
    }

    public static IEnumerable<JToken> DescendantsAndSelf(this JToken root)
    {
        var container = root as JContainer;
        if (container != null)
            return container.DescendantsAndSelf();
        else if (root != null)
            return new[] { root };
        else
            return Enumerable.Empty<JToken>();
    }

    public static JProperty RenameReplace(this JProperty property, string name)
    {
        if (property == null || name == null)
            throw new ArgumentNullException();
        var value = property.Value;
        property.Value = null;
        var newProperty = new JProperty(name, value);
        property.Replace(newProperty);
        return newProperty;
    }

    public static JToken RenameReplaceProperties(this JToken root, Func<string, string> map)
    {
        var query = from property in root.DescendantsAndSelf().OfType<JProperty>()
                    let name = map(property.Name)
                    where name != property.Name && name != null
                    select new KeyValuePair<JProperty, string>(property, name);
        foreach (var pair in query.ToList())
        {
            var newProperty = pair.Key.RenameReplace(pair.Value);
            if (pair.Key == root)
                root = newProperty;
        }
        return root;
    }

    public static XmlDocument ToXmlNode(this JToken root)
    {
        using (var reader = root.CreateReader())
            return DeserializeXmlNode(reader);
    }

    public static XmlDocument DeserializeXmlNode(JsonReader reader)
    {
        return DeserializeXmlNode(reader, null, false);
    }

    public static XmlDocument DeserializeXmlNode(JsonReader reader, string deserializeRootElementName, bool writeArrayAttribute)
    {
        var converter = new Newtonsoft.Json.Converters.XmlNodeConverter() { DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute };
        var jsonSerializer = JsonSerializer.CreateDefault(new JsonSerializerSettings { Converters = new JsonConverter[] { converter } });
        return (XmlDocument)jsonSerializer.Deserialize(reader, typeof(XmlDocument));
    }
}