简体   繁体   中英

XML deserialization with parent object reference

I have an XML file describing a website. It consists of the Site as the root node that can have Pages, Pages can have Objects such as Button or TextBox and Dialogs. Dialogs can also have Objects.

In the corresponding C# classes, all is derived from Element. When I deserialize the XML, how can I have a reference to the Parent of the element getting constructed?

I was told complex types can't de deserialized that way but if I add an ID field to each element in the XML then the parent can be referenced using that and hence be deserialized properly. How would I implement that? I wouldn't want to manually add an ID field to each element in the XML file...

My element class:

public class Element 
{
    public string Name { get; set; }
    public string TagName { get; set; }
    public string XPath { get; set; }

    [XmlElement(ElementName = "Site", Type = typeof(Site))]
    [XmlElement(ElementName = "Page", Type = typeof(Page))]
    [XmlElement(ElementName = "Dialog", Type = typeof(Dialog))]
    public Element Parent { get; set; }

    [XmlArray("Children", IsNullable = false)]
    [XmlArrayItem(Type = typeof(TextBox))]
    [XmlArrayItem(Type = typeof(Page))]
    [XmlArrayItem(Type = typeof(Button))]
    [XmlArrayItem(Type = typeof(Dialog))]
    public Collection<Element> Children { get; set; }

}

My deserialization:

public Site GetSiteFromXml(string filePath, string fileName)
{
    XmlSerializer serializer = new XmlSerializer(typeof(Site));
    return serializer.Deserialize(new XmlTextReader(Path.Combine(filePath, fileName))) as Site;
}

My XML file:

<Site>
  <Name>WebSiteName</Name>
  <Url>https://site.url</Url>
  <Children>
    <Page>
      <Name>HomePage</Name>
      <Address>#page=1</Address>
      <Children>
        <Button>
          <Name>LoginDialogButton</Name>
          <Id>LoginIcon</Id>
          <XPath>//*[@id="LoginIcon"]</XPath>
          <Enabled>true</Enabled>
          <Action>OpenLoginDialog</Action>
        </Button>
        <Dialog>
          <Name>LoginPopUpDialog</Name>
          <Id>loginModal</Id>
          <Children>
            <TextBox>
              <Name>UserNameInput</Name>
            </TextBox>
            <TextBox>
              <Name>PasswordInput</Name>
            </TextBox>
            <Button>
              <Name>LoginButton</Name>
              <Action>DialogDismiss</Action>
            </Button>
          </Children>
        </Dialog>
      </Children>
    </Page>
  </Children>
</Site>

Since you don't have any mechanism in place to guarantee that the Parent and Children properties stay in sync, the easiest way to do this is to tell XmlSerializer to ignore both properties, and add a proxy property that returns the list of children as an array (not a collection). Inside the setter for the proxy property, populate the Children collection and also set the Parent property of the each child:

public class Element
{
    public Element() { Children = new Collection<Element>(); }

    public string Name { get; set; }
    public string TagName { get; set; }
    public string XPath { get; set; }

    [XmlIgnore]
    public Element Parent { get; set; }

    [XmlIgnore]
    public Collection<Element> Children { get; set; }

    [XmlArray("Children", IsNullable = false)]
    [XmlArrayItem(Type = typeof(TextBox))]
    [XmlArrayItem(Type = typeof(Page))]
    [XmlArrayItem(Type = typeof(Button))]
    [XmlArrayItem(Type = typeof(Dialog))]
    [XmlArrayItem(Type = typeof(Site))]
    public Element[] ChildArrayCopy
    {
        get
        {
            return Children.ToArray();
        }
        set
        {
            Children.Clear();
            if (value != null)
                foreach (var child in value)
                    child.SetParent(this);
        }
    }
}

public static class ElementExtensions
{
    public static void SetParent(this Element child, Element parent)
    {
        if (child == null)
            throw new ArgumentNullException();
        if (child.Parent == parent)
            return; // Nothing to do.
        if (child.Parent != null)
            child.Parent.Children.Remove(child);
        child.Parent = parent;
        if (parent != null)
            parent.Children.Add(child);
    }
}

Your XML can now be deserialized and serialized successfully.

Incidentally, I would consider replacing your public Collection<Element> Children { get; set; } public Collection<Element> Children { get; set; } public Collection<Element> Children { get; set; } with public ReadOnlyCollection<Element> Children { get; } public ReadOnlyCollection<Element> Children { get; } . Then keep the children in a private list, and return list.AsReadOnly() . Having done so, you can now guarantee the child list stays up-to-date in the Parent setter:

public class Element
{

    public Element() { }

    public string Name { get; set; }
    public string TagName { get; set; }
    public string XPath { get; set; }

    private readonly List<Element> children = new List<Element>();
    private Element parent = null;

    [XmlIgnore]
    public Element Parent
    {
        get
        {
            return parent;
        }
        set
        {
            if (parent == value)
                return;
            if (parent != null)
                parent.children.Remove(this);
            parent = value;
            if (parent != null)
                parent.children.Add(this);
        }
    }

    [XmlIgnore]
    public ReadOnlyCollection<Element> Children
    {
        get
        {
            return children.AsReadOnly();
        }
    }

    [XmlArray("Children", IsNullable = false)]
    [XmlArrayItem(Type = typeof(TextBox))]
    [XmlArrayItem(Type = typeof(Page))]
    [XmlArrayItem(Type = typeof(Button))]
    [XmlArrayItem(Type = typeof(Dialog))]
    [XmlArrayItem(Type = typeof(Site))]
    public Element[] ChildArrayCopy
    {
        get
        {
            return Children.ToArray();
        }
        set
        {
            if (value != null)
                foreach (var child in value)
                    child.Parent = this;
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM