Десериализация XML с указанием родительского объекта
У меня есть XML-файл с описанием сайта. Он состоит из сайта в качестве корневого узла, который может иметь страницы, а страницы могут иметь такие объекты, как кнопка или TextBox и диалоги. Диалоги также могут иметь объекты.
В соответствующих классах C# все происходит от Element. Когда я десериализирую XML, как я могу получить ссылку на Parent создаваемого элемента?
Мне сказали, что сложные типы не могут быть десериализованы таким образом, но если я добавлю поле ID к каждому элементу в XML, то на родительский элемент можно будет ссылаться с помощью этого и, следовательно, на правильную десериализацию. Как бы я это реализовал? Я не хотел бы вручную добавлять поле ID для каждого элемента в файле XML...
Мой класс элементов:
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; }
}
Моя десериализация:
public Site GetSiteFromXml(string filePath, string fileName)
{
XmlSerializer serializer = new XmlSerializer(typeof(Site));
return serializer.Deserialize(new XmlTextReader(Path.Combine(filePath, fileName))) as Site;
}
Мой XML-файл:
<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>
1 ответ
Поскольку у вас нет какого-либо механизма, чтобы гарантировать, что Parent
а также Children
свойства остаются в синхронизации, самый простой способ сделать это, чтобы сказать XmlSerializer
игнорировать оба свойства и добавить прокси-свойство, которое возвращает список дочерних элементов в виде массива (а не коллекции). Внутри установщика для свойства proxy заполните Children
Коллекция, а также установить Parent
собственность каждого ребенка:
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);
}
}
Ваш XML теперь можно успешно десериализовать и сериализовать.
Кстати, я бы рассмотрел замену вашего public Collection<Element> Children { get; set; }
с public ReadOnlyCollection<Element> Children { get; }
, Затем держите детей в личном списке и возвращайте list.AsReadOnly()
, Сделав это, вы теперь можете гарантировать, что список детей остается актуальным в Parent
сеттер:
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;
}
}
}