Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# XML Data Deserialization - Apply object relationship based on reference ID

I asked a question earlier and I got the tip to use XML deserialization to parse my XML contents to c# objects. After some googling and messing around I got a working deserialization, but I have a question.

My XML file looks like this: (This is just a part of the file)

   <osm>
      <n id="2638006578" l="5.9295547" b="52.5619519" />
      <n id="2638006579" l="5.9301973" b="52.5619526" />
      <n id="2638006581" l="5.9303625" b="52.5619565" />
      <n id="2638006583" l="5.9389539" b="52.5619577" />
      <n id="2638006589" l="5.9386643" b="52.5619733" />
      <n id="2638006590" l="5.9296231" b="52.5619760" />
      <n id="2638006595" l="5.9358987" b="52.5619864" />
      <n id="2638006596" l="5.9335913" b="52.5619865" />
      <w id="453071384">
        <nd rf="2638006581" />
        <nd rf="2638006590" />
        <nd rf="2638006596" />
        <nd rf="2638006583" />
        <nd rf="2638006578" />
      </w>
      <w id="453071385">
        <nd rf="2638006596" />
        <nd rf="2638006578" />
        <nd rf="2638006581" />
        <nd rf="2638006583" />
      </w>
   </osm>

I managed to deserialize this into objects and it works like it should. The problem is as follows: The <nd> elements under the <w> elements have a reference ID which is the same as an ID from a <n> element. It's possible that multiple <w> elements have the same <n> element reference, hence the seperate <n> element.

Currently code-wise I have a NodeReference object that represents the <nd> elements, but I want to directly link the Way class to the Node class based on the reference ID and the Node ID. So basically the Way class should have a List of Nodes rather than a list of NodeReferences. I should have a seperate list of nodes aswell to prevent Ways from having new instances with the same data (e.g. If two Ways have a reference to the same Node they also should point to the same Node instance rather than two identical Node instances, if that makes sense..)

I basically need to access the Lon/Lat/ID fields from a Node instance based on the NodeReference ID.

Here's my code:

DataCollection class

[XmlRoot("osm")]
    public class DataCollection {

        [XmlElement("n")]
        public List<Node> Nodes { get; private set; }

        [XmlElement("w")]
        public List<Way> Ways { get; private set; }

        public DataCollection() {
            this.Nodes = new List<Node>();
            this.Ways = new List<Way>();
        }
    }

The node class

[Serializable()]
public class Node {

    [XmlAttribute("id", DataType = "long")]
    public long ID { get; set; }

    [XmlAttribute("w", DataType = "double")]
    public double Lat { get; set; }

    [XmlAttribute("l", DataType = "double")]
    public double Lon { get; set; }
}

Way

[Serializable()]
    public class Way {

        [XmlAttribute("id", DataType = "long")]
        public long ID { get; set; }

        [XmlElement("nd")]
        public List<NodeReference> References { get; private set; }

        public Way() {
            this.References = new List<NodeReference>();
        }
    }

NodeReference

[Serializable()]
    public class NodeReference {

        [XmlAttribute("rf", DataType = "long")]
        public long ReferenceID { get; set; }
    }

Reading the XML file

public static void Read() {
        XmlSerializer serializer = new XmlSerializer(typeof(DataCollection));
        using (FileStream fileStream = new FileStream(@"path/to/file.xml", FileMode.Open)) {
            DataCollection result = (DataCollection)serializer.Deserialize(fileStream);
            // Example Requested usage: result.Ways[0].Nodes 
        }

        Console.Write("");
    }

Thanks in advance! If you have questions or answers please do let me know!

like image 808
Dubb Avatar asked Dec 08 '16 12:12

Dubb


1 Answers

I agree with @AlexanderPetrov, it's easiest to link the objects after deserialization. I'm using a dictionary for fast lookup and an additional Node property on the NodeReferece class.

var NodeById = this.Nodes.ToDictionary(n => n.ID, n => n);
foreach (var way in this.Ways) {
    foreach (var nd in way.References) {
        nd.Node = NodeById[nd.ReferenceID];
    }
}

The following code is runnable in LINQPad after importing the System.Xml.Serialization namespace via Query Properties.

void Main()
{
    XmlSerializer serializer = new XmlSerializer(typeof(DataCollection));

    using (FileStream fileStream = new FileStream(@"file.xml", FileMode.Open)) {
        DataCollection result = (DataCollection)serializer.Deserialize(fileStream);
        result.Index();

        result.Ways[0].References[0].Node.Lon.Dump();
        // -> 5,9303625
    }
}


// --------------------------------------------------------------------------
[XmlRoot("osm")]
public class DataCollection {
    [XmlElement("n")]
    public List<Node> Nodes = new List<Node>();

    [XmlElement("w")]
    public List<Way> Ways = new List<Way>();

    public void Index() {
        var NodeById = this.Nodes.ToDictionary(n => n.ID, n => n);
        foreach (var way in this.Ways) {
            foreach (var nd in way.References) {
                nd.Node = NodeById[nd.ReferenceID];
            }
        }
    }
}

// --------------------------------------------------------------------------
[Serializable()]
public class Node {
    [XmlAttribute("id", DataType = "long")]
    public long ID { get; set; }

    [XmlAttribute("w", DataType = "double")]
    public double Lat { get; set; }

    [XmlAttribute("l", DataType = "double")]
    public double Lon { get; set; }
}

// --------------------------------------------------------------------------
[Serializable()]
public class Way {
   [XmlAttribute("id", DataType = "long")]
   public long ID { get; set; }

   [XmlElement("nd")]
   public List<NodeReference> References = new List<NodeReference>();
}

// --------------------------------------------------------------------------
[Serializable()]
public class NodeReference {
    [XmlAttribute("rf", DataType = "long")]
    public long ReferenceID { get; set; }

    [XmlIgnore]
    public Node Node { get; set; }
}
like image 73
Tomalak Avatar answered Oct 12 '22 20:10

Tomalak