/// <summary> /// Tries to deserialize an object from an XElement. /// </summary> /// <param name="element"></param> /// <param name="t"></param> /// <param name="setter"></param> /// <param name="newobj"></param> /// <param name="logWarnings">Whether warning messages should be emitted in case of missing properties.</param> /// <returns>True on success.</returns> public virtual bool TryDeserializeObject(XElement element, ITypeData t, Action <object> setter, object newobj = null, bool logWarnings = true) { if (element.IsEmpty && !element.HasAttributes) { setter(null); return(true); } if (newobj == null) { try { newobj = t.CreateInstance(Array.Empty <object>()); t = TypeData.GetTypeData(newobj); } catch (TargetInvocationException ex) { if (ex.InnerException is System.ComponentModel.LicenseException) { throw new Exception(string.Format("Could not create an instance of '{0}': {1}", t.GetAttribute <DisplayAttribute>().Name, ex.InnerException.Message)); } else { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } } } var prevobj = Object; Object = newobj; var t2 = t; if (newobj == null) { throw new ArgumentNullException(nameof(newobj)); } var properties = t2.GetMembers() .Where(x => x.HasAttribute <XmlIgnoreAttribute>() == false) .ToArray(); try { foreach (var prop in properties) { var attr = prop.GetAttribute <XmlAttributeAttribute>(); if (attr == null) { continue; } var name = string.IsNullOrWhiteSpace(attr.AttributeName) ? prop.Name : attr.AttributeName; var attr_value = element.Attribute(XmlConvert.EncodeLocalName(name)); var p = prop as MemberData; if (p != null && attr_value != null && p.Member is PropertyInfo csprop) { try { readContentInternal(csprop.PropertyType, false, () => attr_value.Value, element, out object value); p.SetValue(newobj, value); } catch (Exception e) { if (logWarnings) { Log.Warning(element, "Attribute value '{0}' was not read correctly as a {1}", attr_value.Value, p); Log.Debug(e); } } } } if (properties.FirstOrDefault(x => x.HasAttribute <XmlTextAttribute>()) is IMemberData mem2) { object value; if (mem2.TypeDescriptor is TypeData td && readContentInternal(td.Load(), false, () => element.Value, element, out object _value)) { value = _value; } else { value = StringConvertProvider.FromString(element.Value, mem2.TypeDescriptor, null); } mem2.SetValue(newobj, value); } else { var props = properties.ToLookup(x => x.GetAttributes <XmlElementAttribute>().FirstOrDefault()?.ElementName ?? x.Name); var elements = element.Elements().ToArray(); bool[] visited = new bool[elements.Length]; double order = 0; int foundWithCurrentType = 0; while (true) { double nextOrder = 1000; // since the object might be dynamically adding properties as other props are added. // we need to iterate a bit. Example: Test Plan Reference. int found = visited.Count(x => x); for (int i = 0; i < elements.Length; i++) { var element2 = elements[i]; if (visited[i]) { continue; } if (element2.Attribute(IgnoreMemberXName) is XAttribute attr && attr.Value == "true") { visited[i] = true; continue; } IMemberData property = null; var name = XmlConvert.DecodeName(element2.Name.LocalName); var propertyMatches = props[name]; int hits = 0; foreach (var p in propertyMatches) { if (p.Writable || p.HasAttribute <XmlIgnoreAttribute>()) { property = p; hits++; } } if (0 == hits) { try { if (property == null) { property = t2.GetMember(name); } if (property == null) { property = t2.GetMembers().FirstOrDefault(x => x.Name == name); } } catch { } if (property == null || property.Writable == false) { continue; } hits = 1; } if (hits > 1) { Log.Warning(element2, "Multiple properties named '{0}' are available to the serializer in '{1}' this might give issues in serialization.", element2.Name.LocalName, t.GetAttribute <DisplayAttribute>().Name); } if (property.GetAttribute <DeserializeOrderAttribute>() is DeserializeOrderAttribute orderAttr) { if (order < orderAttr.Order) { if (orderAttr.Order < nextOrder) { nextOrder = orderAttr.Order; } continue; } } else { nextOrder = order; } visited[i] = true; var prev = CurrentMember; CurrentMember = property; try { if (CurrentMember.HasAttribute <XmlIgnoreAttribute>()) // This property shouldn't have been in the file in the first place, but in case it is (because of a change or a bug), we shouldn't try to set it. (E.g. SweepLoopRange.SweepStep) { if (!CurrentMember.HasAttribute <BrowsableAttribute>()) // In the special case we assume that this is some compatibility property that still needs to be set if present in the XML. (E.g. TestPlanReference.DynamicDataContents) { continue; } } if (property is MemberData mem && mem.Member is PropertyInfo Property && Property.PropertyType.HasInterface <IList>() && Property.PropertyType.IsGenericType && Property.HasAttribute <XmlElementAttribute>()) { // Special case to mimic old .NET XmlSerializer behavior var list = (IList)Property.GetValue(newobj); Action <object> setValue = x => list.Add(x); Serializer.Deserialize(element2, setValue, Property.PropertyType.GetGenericArguments().First()); } else { Action <object> setValue = x => { var current = property.GetValue(newobj); property.SetValue(newobj, x); if (false == Equals(current, x)) { // for some value-like type, it may be needed // to set the parent object when a property is changed // example: complex test plan parameters. setter(newobj); } }; Serializer.Deserialize(element2, setValue, property.TypeDescriptor); } }