Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# JsonConvert SerializeXmlNode empty with attributes

Tags:

c#

xml

json.net

I'm using JsonConvert SerializeXmlNode to convert xml to json. The problem that I'm facing with is that I have a tag which sometimes can have value and sometimes be null

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

And as result - I'm getting an exception when trying to deserialize json to C# object with string property "AustrittDatum" - "Newtonsoft.Json.JsonReaderException: 'Error reading string. Unexpected token: StartObject. Path 'AustrittDatum'.' ", because

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

is serialized to

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

How can I force it to be something like this "AustrittDatum": "" or maybe ther is some proper way to resolve it?

like image 367
Roman Borovets Avatar asked Dec 28 '17 14:12

Roman Borovets


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.


1 Answers

It seems as though, when encountering an XML element with xsi:nil="true", Json.NET's XmlNodeConverter creates a JSON object with the attributes you see rather than a null JToken. This is consistent with the Newtonsoft documentation page Converting between JSON and XML:

Converstion 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...

Nevertheless it's reasonable to think that an element with xsi:nil="true" would be converted to a null JSON value, since xsi:nil is a predefined w3c attribute. Possibly Newtonsoft did not do this because such elements can carry additional attributes, which would be lost if the element were to be converted to null.

You could file an enhancement request for XmlNodeConverter if you like, but in the meantime the following extension methods will post-process a JToken hierarchy and convert objects that were formerly nil elements to null JSON values:

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;
    }
}

Then use it like:

// 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>();

Which generates:

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

Here I am initially parsing to an XDocument but you could also use the older XmlDocument

Sample working .Net fiddle.

like image 183
dbc Avatar answered Nov 06 '22 14:11

dbc