1

I am trying to convert an XML data into dictionary. I am having problems with identical node names. C# .Net 3.5

Sample XML = the problem is I have no control over this. I just need to process it.

<?xml version="1.0" encoding="utf-8"?>
<Root>
  <a1>val1</a1>
  <a2>val2</a2>
  <Parameter>
    <ParameterName>param1</ParameterName>
    <ParameterValue>paramval1</ParameterValue>
  </Parameter>
  <Parameter>
    <ParameterName>param2</ParameterName>
    <ParameterValue>paramval2</ParameterValue>
  </Parameter>
</Root>

My attempt:

XMLStream.Position = 0;
        XElement xmlDetails2 = XElement.Load(new System.IO.StreamReader(XMLStream));
        var x = xmlDetails2.Elements().ToDictionary(
            e => e.Name.LocalName,
            e => e.Elements()
                      .ToDictionary(
                          f => f.Name.LocalName,
                          f => f.Value));

Error I am getting (which makes sense of course):

An item with the same key has already been added.

Expected result ( from example xml ) :

a1 => val1
a2 => val2
param1 => paramval1
param2 => paramval2
...

I created my own based on @L.B suggestion. It's not the best solution but it works for now.

public void XMLTODictionary(XElement xmlDetails, ref Dictionary<string, string> dic)
{
    foreach (var node in xmlDetails.Elements())
    {
        if (node.Name.LocalName.Equals("parameter", StringComparison.CurrentCultureIgnoreCase))
        {
                dic.Add(node.Element("ParameterName").Value, node.Element("ParameterValue").Value);
        }
        else
        {
            dic.Add(node.Name.LocalName, node.Value);
        }
    }
}
Picrofo Software
  • 5,475
  • 3
  • 23
  • 37
Flip Booth
  • 271
  • 3
  • 11
  • your expected result sometime use `name` and sometime it uses `value` which makes it difficult to process the xml..you should first collect all name value pairs in a list – Anirudha Dec 06 '12 at 19:57

4 Answers4

0

Unless you provide a unique, non-nullable key, you won't be able to import to a dictionary structure. A couple of options I can think of:

  • Create an artificial key during import
  • Utilize the ToLookup() extension method instead. This doesn't return a dictionary but may suite your needs. Given a better idea of why you need a dictionary, I could possible help more.
Ken
  • 834
  • 9
  • 25
  • I can do without using a dictionary but dictionary seems the most efficient and quickest method. I can always do xmlDetails.Descendants("Parameter") .. then foreach it again to get the values and node names. I just thought there must be a simpler and more efficient way. The purpose is to assign this dictionary to a class member, instead of manually creating class members. For example, object.xml["key"] is simpler that creating individual class members for each parameter. (Hope I made that understandable) – Flip Booth Dec 06 '12 at 19:57
0

How about using DynamicXml here

dynamic root = DynamicXml.Load("a.xml");

Console.WriteLine(root.a1);
foreach (var p in root.Parameter)
{
    Console.WriteLine("{0}:{1}",p.ParameterName, p.ParameterValue);
}
Console.WriteLine(root.Parameter[0].ParameterValue);

EDIT

A generic approach would be to get a dictionary Dictionary<string,object> But there are some problems while converting an xml to dictionary. For example

<a1>val1</a1>

dict["a1"] would return val1, but what would this xml return

<a1 name="valAttr"><name>valName</name><a1>

dict["a1"]["name"] ? valAttr or valName?

And considering your example, the only difference between dict["a1"] and dict["Parameter"] is that Parameter exists more than once under the same parent and it should be thought as an array rather than a single element.

DynamicXml tries to solve these issues. Of course there is a lot of room for improvement but It should work for basic needs.

Community
  • 1
  • 1
L.B
  • 114,136
  • 19
  • 178
  • 224
0

Here is a method which will distill the data into tuples and then output a dictionary. Note it doesn't check for duplicate keys, but that could be added:

string data = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Root>
  <a1>val1</a1>
  <a2>val2</a2>
  <Parameter>
    <ParameterName>param1</ParameterName>
    <ParameterValue>paramval1</ParameterValue>
  </Parameter>
  <Parameter>
    <ParameterName>param2</ParameterName>
    <ParameterValue>paramval2</ParameterValue>
  </Parameter>
</Root>";

var elements = XDocument.Parse( data )
                        .Element("Root")
                        .Descendants();

var asTupleChildren =  elements.Where (e => e.HasElements)
                               .Select (e => new Tuple<string,string>(e.Element("ParameterName").Value, e.Element("ParameterValue").Value ));

var asTupleElements =  elements.Where (e => e.HasElements == false)
                               .Where (e => e.Name != "ParameterName" && e.Name != "ParameterValue" )
                               .Select (e => new Tuple<string,string>(e.Name.ToString(), e.Value ));


var asDictionary =  asTupleElements.Concat(asTupleChildren)
                                   .ToDictionary (te => te.Item1, te => te.Item2);


/* asDictionary is a Dictionary<String,String> (4 items)

a1 val1
a2 val2
param1 paramval1
param2 paramval2

*/
ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
0

just made this, works for me. bit clunky. use it as a template.

        public static Dictionary<string, dynamic> RecDis(string ThisXML)
    {
        Dictionary<string, dynamic> ThisBlock = new Dictionary<string, dynamic>();

        XElement doc = XElement.Parse(ThisXML);

        XNode[] ThisNoideArray = doc.Nodes().ToArray();

        foreach (XNode park in ThisNoideArray)
        {
            XElement parker = XElement.Parse(park.ToString());

            if (parker.HasElements)
            {
                ThisBlock.Add(parker.Name.ToString(), RecDis(parker.ToString()));
            }
            else
            {
                ThisBlock.Add(parker.Name.ToString(), parker.Value.ToString());
            }
        }

        return ThisBlock;
    }