How to generate XML from JSON with parent node of array items - c#

I am trying to create an XML document that closely conforms to a C# object graph and its JSON representation, but am having difficulty with the list representation in the XML. Given this graph
public class X
{
public List<A> Aa { get; set; }
}
public class A
{
public int B;
public bool C;
}
I took the JSON from the above, and tried converting it a couple of ways:
var json = #"{""Aa"":[{""B"":186,""C"":true},{""B"":9,""C"":false},{""B"":182,""C"":true}]}";
var xml = JsonConvert.DeserializeXNode(json, typeof(T).Name, false);
var xml2 = JsonToXml(json);
This produced the following for xml (no Aa "container node"):
<X>
<Aa><B>186</B><C>true</C></Aa>
<Aa><B>9</B><C>false</C></Aa>
<Aa><B>182</B><C>true</C></Aa>
</X>
And for xml2 (has "container" node, but some extra noise):
<root type="object">
<Aa type="array">
<item type="object">
<B type="number">186</B>
<C type="boolean">true</C>
</item>
<item type="object">
<B type="number">9</B>
<C type="boolean">false</C>
</item>
<item type="object">
<B type="number">182</B>
<C type="boolean">true</C>
</item>
</Aa>
</root>
The method used to produce the value for xml2 comes from a different approach using the .NET Framework:
XDocument JsonToXml(string jsonString)
{
using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(jsonString)))
{
var quotas = new XmlDictionaryReaderQuotas();
return XDocument.Load(JsonReaderWriterFactory.CreateJsonReader(stream, quotas));
}
}
What I want to produce is
<X>
<Aa>
<A><B>186</B><C>true</C></A>
<A><B>9</B><C>false</C></A>
<A><B>182</B><C>true</C></A>
</Aa>
</X>
I have tried changing the writeArrayAttribute parameter of DeserializeXDocument to true, but that doesn't work either. The documentation for converting between JSON and XML does not help.
How can I produce the compact version that contains the items in a parent Aa node? Is this going to require some custom deserializer?
The original JSON was created via
var json = JsonConvert.SerializeObject(new X { etc }, Formatting.None, settings);

The Problem.
Your difficulty arises because there are two common ways to serialize a collection to XML, and Json.NET only supports automatic JSON-to-XML conversion for one of them.
Specifically, when serializing a c# collection to XML (with, say, XmlSerializer), the collection can be serialized either with, or without, an outer container element. The former looks like the following:
<X>
<Aa>
<A>
<B>186</B>
<C>true</C>
</A>
<A>
<B>9</B>
<C>false</C>
</A>
</Aa>
</X>
While the latter looks like:
<X>
<Aa>
<B>186</B>
<C>true</C>
</Aa>
<Aa>
<B>9</B>
<C>false</C>
</Aa>
</X>
When Json.NET converts a JSON array to XML elements, it uses the second format for the array, since the JSON only contains one property name while the two-level XML format requires both inner and outer element names. I.e. in your JSON:
{"Aa":[{"B":186,"C":true},{"B":9,"C":false}]}
Only the name "Aa" appears. The name "A" never does, so DeserializeXNode() cannot know to insert it. This makes the second format the straightforward choice for canonical conversion, whereas you require the first.
The Solution.
To generate a two-level XML collection from a JSON array, you'll need to either insert synthetic JSON objects before conversion, or synthetic XML elements afterwards. For the former, this can be done by parsing the JSON string to an intermediate JToken, and modifying it as follows:
var jObject = JObject.Parse(json);
jObject.SelectTokens("Aa").WrapWithObjects("A");
var finalXml = jObject.ToXElement(typeof(X).Name, false);
Using the extension methods:
public static class JsonExtensions
{
public static void WrapWithObjects(this IEnumerable<JToken> values, string name)
{
foreach (var value in values.ToList())
{
var newParent = new JObject();
if (value.Parent != null)
value.Replace(newParent);
newParent[name] = value;
}
}
public static XElement ToXElement(this JObject obj, string deserializeRootElementName = null, bool writeArrayAttribute = false)
{
if (obj == null)
return null;
using (var reader = obj.CreateReader())
return JsonExtensions.DeserializeXElement(reader, deserializeRootElementName, writeArrayAttribute);
}
static XElement DeserializeXElement(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 jsonSerializer.Deserialize<XElement>(reader);
}
}
Alternatively, you could tell XmlSerializer to (de)serialize the Aa list without a container element by marking it with [XmlElement]:
public class X
{
[XmlElement]
public List<A> Aa { get; set; }
}
Now the xml generated by JsonConvert.DeserializeXNode will be deserializable directly.

Related

Json data remove unwanted elements

I have the following in some json I have converted from XML:
"ForSaleStatus":{"items":{"item":{"label":"Not For Sale","value":"2"}}}
This was some xml an application generated from a dropdown. I want to convert it to this in C#:
"ForSaleStatus":"2"
Here is the code I have so far:
var xml = i;
var root = XElement.Parse(xml);
// remove ALL empty elements such as <items/>
root.Descendants().Where(e => string.IsNullOrEmpty(e.Value)).Remove();
// convert the node to JSON for easier processing
var json = JsonConvert.SerializeXNode(root);
Can anyone think of a way of converting the json or am I better off going back to the XML and working on that?
The XML looks like this:
<ForSaleStatus>
<items>
<item>
<label>Not For Sale</label>
<value>2</value>
</item>
</items>
</ForSaleStatus>
It would be easier to extract the values you want and just construct the json from that :
var json = JsonConvert.SerializeObject(new Dictionary<string, string>
{
[root.Name.ToString()] = root.XPathSelectElement("items/item/value").Value
});
If that doesn't compile for you, use this older syntax :
var json = JsonConvert.SerializeObject(new Dictionary<string, string>
{
{ root.Name.ToString(), root.XPathSelectElement("items/item/value").Value }
});
Edit: in case you need values from all the items :
var json = JsonConvert.SerializeObject(root
.XPathSelectElements("items/item")
.ToDictionary(
x => x.XPathSelectElement("label").Value,
x => x.XPathSelectElement("value").Value
));

C# Serialize List to XML with Sequence attribute autogenerated?

I have to generate (serialize to) XML from object with array of (Order) elements.
Order class generated from XSD has sequence attribute:
[System.Xml.Serialization.XmlAttributeAttribute(DataType = "token")]
public string Sequence;
I am using .Net XMLSerializer, but it does not generate automagically for every Order element Sequence attribute.
Having:
Order[] Orders = new Order[2] {...}
I have to get:
<Order Sequence="1">..</Order>
<Order Sequence="2">..</Order>
And for just one, single element it should render:
<Order Sequence="1">..</Order>
Does anyone know how to make XMLSerialzier renders this attribute automagically? Or do I need to manually set Sequence for every Order element?
Cheers
AFAIK there is no way to achieve this with out-of-the-box methods. This leaves you with the following options:
IXmlSerializable
Implement IXmlSerializable on the object that contains the array of Orders. This way, you can either serialize the Orders manually or set the sequence number of the Order before serializing the object into the XmlWriter using the XmlSerializer.Serialize method:
public class OrdersContainer : IXmlSerializable
{
public Order[] Orders;
public void WriteXml(XmlWriter writer)
{
// Serialize other properties
writer.WriteStartElement("Orders");
var ser = new XmlSerializer(typeof(Order));
for(var i = 0; i < Orders.Length; i++)
{
Orders[i].Sequence = (i + 1).ToString();
ser.Serialize(writer, Orders[i]);
}
writer.WriteEndElement(); // Orders
}
// ...
}
This generated the following XML:
<?xml version="1.0" encoding="utf-16"?>
<OrdersContainer>
<Orders>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Sequence="1">
<Name>Order 1</Name>
</Order>
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Sequence="2">
<Name>Order 2</Name>
</Order>
</Orders>
</OrdersContainer>
The XmlSerializer places some namespace declarations on the Order elements, but that doesn't hurt. My sample class had a Name property on the Order class.
The downside of this approach is that you have to implement the serialization of the OrdersContainer class manually, including the deserialization.
Linq to XML
The second option is to use Linq to XML for the serialization part. You'd also have to implement the serialization manually, but you could use the otb XmlSerializer for deserialization (on the other hand you might want to avoid to mix two different frameworks). You could serialize the OrdersContainer class as follows and also write the sequence number:
var doc = new XDocument(new XElement("OrdersContainer",
new XElement("Orders",
cont.Orders.Select((x, i) => new XElement("Order",
new XAttribute("Sequence", (i + 1).ToString()),
new XElement("Name", x.Name))))));
doc.Save(writer);
This created the following XML:
<?xml version="1.0" encoding="utf-16"?>
<OrdersContainer>
<Orders>
<Order Sequence="1">
<Name>Order 1</Name>
</Order>
<Order Sequence="2">
<Name>Order 2</Name>
</Order>
</Orders>
</OrdersContainer>
Please note that the sample uses an overload of the Select method that also receives the index of the item so that the sequence number can be created based upon the position in the array.
If order is a class of yours, you can add a property with [XmlAttributeAttribute]
class Order {
[XmlAttribute()]
public int Sequence { get; set; }
But you should set this Sequence property in all items of your list.
this works fine -
var o = new XmlSerializerNamespaces();
o.Add("", "");
var ser = new XmlSerializer(typeof(List<Order>), "");
using (var sw = new StringWriter())
{
ser.Serialize(sw, new List<Order> {
new Order { sequence = "1", MyProperty = 1 },
new Order { sequence = "2", MyProperty = 2 } },
o);
var t = sw.ToString();
}
AND
[XmlAttribute(AttributeName = "sequence", DataType = "string")]
public string sequence { get; set; }

List is empty after parsing XML with LinQ

I have an xml file similar to the following:
<doc>
<file>
<header>
<source>
RNG
</source>
</header>
<body>
<item name="items.names.id1">
<property>propertyvalue1</property>
</item>
<!-- etc -->
<item name="items.names.id100">
<property>propertyvalue100</property>
</item>
<!-- etc -->
<item name="otheritems.names.id100">
<property>propertyvalue100</property>
</item>
</body>
</file>
</doc>
And the following class:
private class Item
{
public string Id;
public string Property;
}
The file has, for example, 100 item entries (labeled 1 to 100 in the name attribute). How can I use Linq Xml to get hold of these nodes and place them a in list of item?
Using Selman22's example, I'm doing the following:
var myList = xDoc.Descendants("item")
.Where(x => x.Attributes("name").ToString().StartsWith("items.names.id"))
.Select(item => new Item
{
Id = (string)item.Attribute("name"),
Name = (string)item.Element("property")
}).ToList();
However, the list is empty. What am I missing here?
Using LINQ to XML:
XDocument xDoc = XDocument.Load(filepath);
var myList = xDoc.Descendants("item").Select(item => new Item {
Id = (string)item.Attribute("name"),
Property = (string)item.Element("property")
}).ToList();
You can use LinqToXml to directly query the XML, or deserialize it and use LINQ to object. If you choose to deserialize I suggest to start from the schema and generate the classes representing your datamodel with xsd.exe. If you don't have the schema of your xml, even xsd.exe can infer one from an example xml file, but you probably need to fine tune the result.
Try this one XElement root = XElement.Parse("your file name");
var items textSegs =(from item in root.Descendants("item")
select item).ToList();
Now iterate over list and store it
The below is a way of getting information from xml using Xdocument.
string input = "<Your xml>";
Xdocument doc = XDocument.Parse(input);
var data = doc.Descendants("item");
List<Items> itemsList = new List<Items>();
foreach(var item in data)
{
string itemname= item.Element("item").Value;
string property = item.Element("property").Value;
itemsList.Add(new item(itemname, property));
}
I'm guessing you want the code given how your question is phrased.. also I'm assuming the real XML is very simplistic as well.
var items = from item in doc.Descendants("item")
select new Item()
{
Id = item.Attributes("name").First().Value,
Property = item.Elements().First().Value,
};
Just ensure that your xml is loaded into doc. You can load the xml in two ways:
// By a string with xml
var doc = XDocument.Parse(aStringWithXml);
// or by loading from uri (file)
var doc = XDocuemnt.Load(aStringWhichIsAFile);

C# object to XML

I am creating an application which requires to convert c# object to XML.
I am using XML Serializer class to achieve this. Here is the code snippet:
public class Anwer
{
public int ID { get; set; }
public string XML { get; set; }
public Anwer(int ID, string XML)
{
this.ID = ID;
this.XML = XML;
}
public Anwer() { }
}
Here is the main function:
string AnswerXML = #"<Answer>1<Answer>";
List<Anwer> answerList = new List<Anwer>();
answerList.Add(new Anwer(1,AnswerXML));
AnswerXML = #"<Answer>2<Answer>";
answerList.Add(new Anwer(2, AnswerXML));
XmlSerializer x = new XmlSerializer(answerList.GetType());
x.Serialize(Console.Out, answerList);
The output is:
<?xml version="1.0" encoding="IBM437"?>
<ArrayOfAnwer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="h
ttp://www.w3.org/2001/XMLSchema">
<Anwer>
<ID>1</ID>
<XML><Answer>1<Answer></XML>
</Anwer>
<Anwer>
<ID>2</ID>
<XML><Answer>2<Answer></XML>
</Anwer>
</ArrayOfAnwer>
In the above code '<' and '>' are getting replaced by '<' and '&gt';
How to avoid this?
I know string replace is one of the way, but I don't want to use it.
Thanks in advance.
You don't, basically. That's correctly serializing the object - the XML serializer doesn't want to have to deal with XML within strings messing things up, so it escapes the XML appropriately.
If you deserialize the XML later, you'll get back to the original object data.
If you're trying to build up an XML document in a custom fashion, I suggest you don't use XML serialization to start with. Either use LINQ to XML if you're happy to create elements etc explicitly, or if you really, really want to include arbitrary strings directly in your output, use XmlWriter.
If you could give us more information about the bigger picture of what you're trying to do, we may be able to suggest better alternatives - building XML strings directly is almost never a good idea.
XmlSerializer won't believe you that an element is xml unless you convince it, for example by exposing that property as an XmlDocument. Otherwise, it (correctly, IMO) always encodes such values. For example:
using System;
using System.Xml;
using System.Xml.Serialization;
public class Anwer
{
public int ID { get; set; }
public XmlDocument XML { get; set; }
public Anwer(int ID, string XML)
{
this.ID = ID;
XmlDocument doc = new XmlDocument();
doc.LoadXml(XML);
this.XML = doc;
}
public Anwer()
{ }
}
static class Program
{
static void Main()
{
var answer = new Anwer(123, "<Answer>2</Answer>");
var ser = new XmlSerializer(answer.GetType());
ser.Serialize(Console.Out, answer);
}
}
I am creating an application which requires to convert c# object to XML. I am using XML Serializer class to achieve this
If you're using the XML Serializer to do the work, then why the "XML" field where you're inserting hand-coded XML? Seems like you want something more like this (using your class name, though it looks like a misspelling):
public class Anwer
{
public int ID { get; set; }
public int Answer { get; set; }
}
..
List<Anwer> answerList = new List<Anwer>() {
new Anwer { ID=1, Answer=2 },
new Anwer { ID=2, Answer=3 },
};
XmlSerializer x = new XmlSerializer(answerList.GetType());
x.Serialize(Console.Out, answerList);
..
<ArrayOfAnwer ...>
<Anwer>
<ID>1</ID>
<Answer>2</Answer>
</Anwer>
...
Or if you actually want/need the Answer element to be nested in an XML element for some reason, you can alter your Anwer object to reflect that structure (as Oleg Kalenchuk suggests), or generate the XML yourself rather than using the serializer:
XElement xml = new XElement("AnwerList",
from anwer in anwerList select
new XElement("Anwer",
new XElement("ID", anwer.ID),
new XElement("XML",
new XElement("Answer", anwer.Answer)
)
)
);
Console.Out.WriteLine(xml);
<AnwerList>
<Anwer>
<ID>1</ID>
<XML>
<Answer>2</Answer>
</XML>
</Anwer>
...
I prefer the latter anyway, because it gives you more control.
You're assigning a string containing the < and > sign to the XML element so it is obvious that teh serializer would replace the < and > with entity references. Even if you're getting > in the text when you deserialise the XML you'll get the > in your text.
Create a new class AnswerXML with one integer "Answer" member
Change type of XML member to AnswerXML instead of string
Because '<' and '>' are characters used for the xml-structure itself, they are automatically htmlencoded. When you read it back in your app and deserialize it, the '<' and '>' should be converted back to '<' and '>'.
If your goal is otherwise, use htmldecode functionality.
If this don't help, just tell what exactly you want to do with the xml-data.

XmlSerializer Deserializing Array/List of Multiple Objects at Root

I'm trying to deserialize the following XML output:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Foo>
<Val>Data1</Val>
</Foo>
<Foo>
<Val>Data2</Val>
</Foo>
(This is output from a hardware device, and cannot be changed)
I have an XML type defined as:
[XmlType(AnonymousType=true, Namespace="")]
public class Foo
{
public string Val { get; set; }
}
I've tried to deserialize this array by creating a serializer like:
var s = new XmlSerializer(typeof(Foo[]));
//or
var s = new XmlSerializer(typeof(List<Foo>);
But every call to s.Deserialize() causes an InvalidOperaitonException:
System.InvalidOperationException: <Foo xmlns=''> was not expected.
Note
var s = new XmlSerializer(typeof(Foo));
// Only deseralizes the first Foo (Data1).
Thanks for your help.
I think the issue is with your provided xml.
Test app says
List<Foo> list = new List<Foo> {new Foo {Val = "Data1"}, new Foo {Val = "Data2"}};
var s = new XmlSerializer(typeof(List<Foo>));
StringBuilder sb = new StringBuilder();
XmlWriter wr = XmlWriter.Create(sb);
s.Serialize(wr, list);
string ss = sb.ToString();
var s2 = new XmlSerializer(typeof(List<Foo>));
StringReader sr = new StringReader(ss);
List<Foo> returnList = (List<Foo>)s2.Deserialize(sr);
And the XML should be
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfFoo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Foo>
<Val>Data1</Val>
</Foo>
<Foo>
<Val>Data2</Val>
</Foo>
</ArrayOfFoo>
If you can remove the inital line
<?xml version="1.0" encoding="ISO-8859-1"?>
And minipulate the string into
string s = "<ArrayOfFoo><Foo> <Val>Data1</Val></Foo><Foo> <Val>Data2</Val></Foo></ArrayOfFoo>";
var s2 = new XmlSerializer(typeof(List<Foo>));
StringReader sr = new StringReader(s);
List<Foo> list = (List<Foo>)s2.Deserialize(sr);
That could work.
That isn't valid Xml. There needs to be a core root element for it to work properly.
this is not a valid xml so you can not deserialize it like a valid xml. You need some kind of hack to make this work. i'd suggest to insert at beginning of the xml and inserting at the end of xml. then you can deserialize it, since you cant make this change at xml side, do it in your code.
String ss;
// lets assume this holds your xml data in string.
ss.append("</ArrayOfFoo>");
ss.replace("<?xml version=\"1.0\" encoding=\"utf-16\"?>", "<?xml version=\"1.0\" encoding=\"utf-16\"?> <ArrayOfFoo>")
var s2 = new XmlSerializer(typeof(List<Foo>));
StringReader sr = new StringReader(ss);
List<Foo> returnList = (List<Foo>)s2.Deserialize(sr);
now this shall return you the correct list.
As the other posters say, this XML that the hardware device produces is not compatible to the way .NET serializes/deserializes object. Valid XML, and .NET requires valid XML, has a root element.
I suggest:
either you modify your obtained XML to match the way astander presents in his xml code snippet.
or you write a simple xml parser for your file that deserializes the file like you need
br, Marcel
Technically, what you have there is not a well-formed XML document (which has exactly one root element), but rather an well-formed external parsed entity (which can have any number of elements not contained in other elements, as well as text not contained in any elements). Therefore, it should parse if your XML parser has an entry point for parsing EPEs rather than documents. You could also create a stub document which includes by reference your EPE and parse that document.
Xstream for .Net could be a useful API

Categories