1

Recently, I started to try and use the Newtonsoft package in order to convert XML to JSON format. This package is used after adding it through the NuGet in VS2017.

I try to perform the following:

string jsonText = JsonConvert.SerializeXmlNode(myNode);

(myNode is part of the XML tree).

I noticed that the result in JSON always ends-up with key names number sign (#) or the at character (@).

Can anyone help and tell me how can I avoid these characters and get key names without one of these two characters?

dbc
  • 104,963
  • 20
  • 228
  • 340
Dan
  • 11
  • 2

1 Answers1

1

This is documented functionality. From Converting between JSON and XML:

Conversion Rules:

  • Elements remain unchanged.

  • Attributes are prefixed with an @ and should be at the start of the object.

  • Single child text nodes are a value directly against an element, otherwise they are accessed via #text.

  • The XML declaration and processing instructions are prefixed with ?.

  • Character data, comments, whitespace and significant whitespace nodes are accessed via #cdata-section, #comment, #whitespace and #significant-whitespace respectively.

  • Multiple nodes with the same name at the same level are grouped together into an array.

  • Empty elements are null.

If the XML created from JSON doesn't match what you want, then you will need to convert it manually.

If you don't want this, you can convert your XML to an intermediate JToken hierarchy and use JsonExtensions.RenameProperties() from json.net custom jobject deserialization to strip the unwanted @ and # characters from the property names after conversion.

First, define the following extension methods:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;

public static partial class JsonExtensions
{
    public static JToken ToJToken(this XmlNode node, bool omitRootObject = false, string deserializeRootElementName = null, bool writeArrayAttribute = false)
    {
        // Convert to Linq to JSON JObject
        var settings = new JsonSerializerSettings { Converters = { new XmlNodeConverter { OmitRootObject = omitRootObject, DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute } } };
        var root = JToken.FromObject(node, JsonSerializer.CreateDefault(settings));
        return root;
    }

    public static TJToken StripXmlPrefixCharacters<TJToken>(this TJToken root) where TJToken : JToken
    {
        return root.RenameProperties(s => s = s.TrimStart('@').TrimStart('#'));
    }

    // Taken from https://stackoverflow.com/questions/42180161/json-net-custom-jobject-deserialization/42180741#42180741 by dbc

    public static TJToken RenameProperties<TJToken>(this TJToken root, Func<string, string> map) where TJToken : JToken
    {
        if (map == null)
            throw new ArgumentNullException();
        if (root == null)
            return null;
        if (root is JProperty)
        {
            return RenameReplaceProperty(root as JProperty, map, -1) as TJToken;
        }
        else
        {
            foreach (IList<JToken> obj in root.DescendantsAndSelf().OfType<JObject>())
                for (int i = obj.Count - 1; i >= 0; i--)
                    RenameReplaceProperty((JProperty)obj[i], map, i);
            return root;
        }
    }

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

    private static JProperty RenameReplaceProperty(JProperty property, Func<string, string> map, int index)
    {
        // JProperty.Name is read only so it will need to be replaced in its parent.
        if (property == null)
            return null;
        var newName = map(property.Name);
        if (newName == property.Name)
            return property;
        var value = property.Value;
        // Setting property.Value to null on the old property prevents the child JToken hierarchy from getting recursively cloned when added to the new JProperty
        // See https://github.com/JamesNK/Newtonsoft.Json/issues/633#issuecomment-133358110
        property.Value = null;
        var newProperty = new JProperty(newName, value);
        IList<JToken> container = property.Parent;
        if (container != null)
        {
            if (index < 0)
                index = container.IndexOf(property);
            container[index] = newProperty;
        }
        return newProperty;
    }
}

Then do:

var token = myNode.ToJToken().StripXmlPrefixCharacters();
var jsonText = token.ToString(Newtonsoft.Json.Formatting.Indented); // Or use Formatting.None if you don't want indenting

Working sample .Net fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • What's the purpose of this section in DescendantsAndSelf: `var container = node as JContainer; if (container != null) return container.DescendantsAndSelf();` This causes an infinite loop for my xml. – Mideus Feb 26 '20 at 17:04
  • @Mideus - `JContainer` has a instance method [`JContainer.DescendantsAndSelf()`](https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Linq_JContainer_DescendantsAndSelf.htm). The purpose is to call that method when the incoming `JToken` is a `JContainer`, but if not, simply return the token itself. There should be no infinite loop since [instance methods supersede extension methods](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods#binding-extension-methods-at-compile-time). – dbc Feb 26 '20 at 22:36