internal static object DeserializeToCSharpObject(IEnumerable <XNode> content, Type resultType, XElement parentElement, IReadOnlyList <Type> knownTypes, bool ignoreErrors, bool useXmlSerializerFormat) { if (resultType.IsGenericType && resultType.GetGenericTypeDefinition() == typeof(Nullable <>)) { resultType = resultType.GetGenericArguments()[0]; } Type itemsType; //if (resultType.IsValueType || resultType == typeof(string)) if (resultType.IsEnum || DataContractSerializer_ValueTypesHandler.TypesToNames.ContainsKey(resultType)) { //------------------------- // VALUE TYPE OR STRING //------------------------- return(DeserializeToCSharpObject_ValueTypeOrString(content, resultType)); } else if (resultType.IsArray && resultType.GetElementType() == typeof(Byte)) { //------------------------- // BYTE ARRAY //------------------------- return(DeserializeToCSharpObject_ByteArray(content)); } else if (DataContractSerializer_Helpers.IsAssignableToGenericEnumerableOrArray(resultType, out itemsType)) { //------------------------- // IEnumerable<T> or array (WITH RECURSION) //------------------------- return(DeserializeToCSharpObject_Enumerable_WithRecursion(content, resultType, knownTypes, ignoreErrors, itemsType, useXmlSerializerFormat)); } else { //------------------------- // OTHER OBJECT TYPE (WITH RECURSION) //------------------------- return(DeserializeToCSharpObject_Object_WithRecursion(content, resultType, parentElement, knownTypes, ignoreErrors, useXmlSerializerFormat)); } throw new Exception("Unable to create the type '" + resultType.ToString() + "'."); }
static List <XNode> SerializeToXNodes_Enumerable_WithRecursion(object obj, Type objectType, IReadOnlyList <Type> knownTypes, bool useXmlSerializerFormat, string nodeDefaultNamespaceIfAny, bool isRoot, bool isContainedInsideEnumerable) { List <XNode> result = new List <XNode>(); // Get the type information (namespace, etc.) by reading the DataContractAttribute and similar attributes, if present: TypeInformation typeInformation = DataContractSerializer_Helpers.GetTypeInformationByReadingAttributes(objectType, nodeDefaultNamespaceIfAny); // Traverse the collection: foreach (object item in (IEnumerable)obj) { //todo: USEMETHODTOGETTYPE: make a method that returns the actual type of object and replace all USEMETHODTOGETTYPE with a call to this method. (also find other places where we could use it) Type itemType = item.GetType(); if (item is char) //special case because JSIL thinks a variable of type object containing a char contains a string. { itemType = typeof(char); } //********** RECURSION ********** //WILD GUESS: Since we are going to the content of an IEnumerable, the namespace of the IEnumerable becomes irrelevant for its content so we give them null instead. List <XNode> xnodesForItem = SerializeToXNodes(item, itemType, knownTypes, useXmlSerializerFormat, isRoot: false, isContainedInsideEnumerable: true, parentTypeInformation: typeInformation, nodeDefaultNamespaceIfAny: typeInformation.NamespaceName); // Keep only the first node: if (xnodesForItem.Count > 1) { throw new Exception("When serializing an IEnumerable, we do not expect an item of the enumerable to be serialized as multiple XNodes."); } XNode nodeForItem = xnodesForItem.First(); // If it's a value type, add an XElement to surround the node: if (nodeForItem is XText) { //todo: do we still ever enter this block? In fact, we now add the surrounding XElement in other places. //------------ // Value type //------------ //we assume namespace of the element is "http://schemas.microsoft.com/2003/10/Serialization/Arrays" as that is what it is for both an array and a list of string //the name of the element should be found in the Dictionary Type --> XName with the item's type //Note: We shoud not arrive here if we cannot find the name in the Dictionary mentioned (except maybe for structs but we'll see when we'll deal with them). XName elementName = XNamespace.Get("http://schemas.microsoft.com/2003/10/Serialization/Arrays").GetName(DataContractSerializer_ValueTypesHandler.TypesToNames[item.GetType()]); //in this line, we create a XName with the namespace that seems to be used for all Enumerables and a name associated with the type. XElement element = new XElement(elementName); // name of the element: use the Dictionary Type --> XName with the item's type element.Add(nodeForItem); nodeForItem = element; } // Add the node to the resulting collection of nodes: result.Add(nodeForItem); } // If the value is the root, add an XElement "ArrayOf..." to surround the nodes: if (isRoot) { Type itemsType; if (DataContractSerializer_Helpers.IsAssignableToGenericEnumerableOrArray(objectType, out itemsType)) { string elementName = "Array"; if (DataContractSerializer_ValueTypesHandler.TypesToNames.ContainsKey(itemsType)) { elementName = "ArrayOf" + DataContractSerializer_ValueTypesHandler.TypesToNames[itemsType]; } else { // In case of nested types, replace the '+' with '.', and do other changes to obtain the type name to use in the serialization: string itemsTypeName = DataContractSerializer_Helpers.GetTypeNameSafeForSerialization(itemsType); elementName = "ArrayOf" + itemsTypeName; } XElement xElement = new XElement(XNamespace.Get("http://schemas.microsoft.com/2003/10/Serialization/Arrays").GetName(elementName), result); xElement.Add(new XAttribute(XNamespace.Get(DataContractSerializer_Helpers.XMLNS_NAMESPACE).GetName("xmlns:i"), DataContractSerializer_Helpers.XMLSCHEMA_NAMESPACE)); return(new List <XNode>() { xElement }); } else { throw new SerializationException(string.Format("The type '{0}' cannot be serialized because it does implement IEnumerable<T> nor is it an Array.", objectType.ToString())); //todo: see if we can avoid this limitation. } } return(result); }
static object DeserializeToCSharpObject_Object_WithRecursion(IEnumerable <XNode> content, Type resultType, XElement parentElement, IReadOnlyList <Type> knownTypes, bool ignoreErrors, bool useXmlSerializerFormat) { // Quit if attempting to deserialize to an interface: //todo: investigate why this may happen. if (!resultType.IsInterface) { string resultTypeFullName = resultType.FullName; // For debugging only, can be removed. // Create the resulting class: object resultInstance = Activator.CreateInstance(resultType); //todo: replace with "System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type)" so that the type does not require a parameterless constructor. // Call the "OnDeserializing" method if any: CallOnDeserializingMethod(resultInstance, resultType); // Get the type information (namespace, etc.) by reading the DataContractAttribute and similar attributes, if present: TypeInformation typeInformation = DataContractSerializer_Helpers.GetTypeInformationByReadingAttributes(resultType, null); // Read the members of the target type: IEnumerable <MemberInformation> membersInformation = DataContractSerializer_Helpers.GetDataContractMembers(resultType, typeInformation.serializationType, useXmlSerializerFormat); // Make a dictionary of the members of the target type for faster lookup: Dictionary <string, MemberInformation> memberNameToMemberInformation = new Dictionary <string, MemberInformation>(); foreach (var memberInformation in membersInformation) { string memberName = memberInformation.Name; if (resultType.FullName.StartsWith("System.Collections.Generic.KeyValuePair")) { if (memberName == "key") { memberName = "Key"; } else if (memberName == "value") { memberName = "Value"; } } if (!memberNameToMemberInformation.ContainsKey(memberName)) { memberNameToMemberInformation.Add(memberName, memberInformation); } else { MemberInformation collidingMemberInformation = memberNameToMemberInformation[memberName]; throw new InvalidDataContractException( string.Format( "Type '{0}' contains two members '{1}' 'and '{2}' with the same data member name '{3}'. Multiple members with the same name in one type are not supported. Consider changing one of the member names using DataMemberAttribute attribute.", resultType.ToString(), memberInformation.MemberInfo.Name, collidingMemberInformation.MemberInfo.Name, memberName )); } } // Populate the values of the properties/members of the class: HashSet2 <string> membersForWhichWeSuccessfullSetTheValue = new HashSet2 <string>(); foreach (XNode node in content) { if (node is XElement) // Normally an object property was serialized as an XElement. { XElement xElement = (XElement)node; XName elementName = xElement.Name; string elementNameWithoutNamespace = elementName.LocalName; // Find the member that has the name of the XNode: MemberInformation memberInformation; if (memberNameToMemberInformation.TryGetValue(elementNameWithoutNamespace, out memberInformation)) { // Avoid processing nodes that have the same name as other nodes already processed (this can happen in case of [XmlElement] attribute on enumerable members - cf. "special case" below - but it is handled differently): if (!membersForWhichWeSuccessfullSetTheValue.Contains(memberInformation.Name)) { object memberValue = null; Type memberActualType = memberInformation.MemberType; // Note: this is the initial value. It may be modified below. if (DataContractSerializer_Helpers.IsElementNil(xElement)) { //---------------------- // XNode is "Nil", so we return the default value of the result type //---------------------- memberValue = DataContractSerializer_Helpers.GetDefault(memberInformation.MemberType); } else { bool isNull = false; //foreach (XAttribute attribute in xElement.Attributes(XNamespace.Get("http://www.w3.org/2001/XMLSchema-instance").GetName("nil"))) //doesn't work... //todo: try removing this foreach since it should be handled in the "if(IsElementDefaut(xElement))" above. foreach (XAttribute attribute in xElement.Attributes("nil")) //We have to do this here because those usually do not have nodes, which causes problems when doing the recursion. { isNull = Convert.ToBoolean(attribute.Value); if (isNull) { memberValue = null; } } if (!isNull) { memberActualType = DataContractSerializer_KnownTypes.GetCSharpTypeForNode(xElement, memberInformation.MemberInfo.DeclaringType, memberActualType, knownTypes, memberInformation); //if the type is nullable, we get the undelying type: Type nonNullableMemberType = memberActualType; if (memberActualType.FullName.StartsWith("System.Nullable`1")) { nonNullableMemberType = Nullable.GetUnderlyingType(memberActualType); } // Recursively create the value for the property: IEnumerable <XNode> propertyChildNodes = xElement.Nodes(); //********** RECURSION ********** memberValue = DeserializeToCSharpObject(propertyChildNodes, nonNullableMemberType, xElement, knownTypes, ignoreErrors, useXmlSerializerFormat); } //--------------------------------- // Handle the special case where there is an [XmlElement] attribute on an enumerable member (XmlSerializer compatibility mode only): // // Example: // <MyObject> // <MyEnumerablePropertyName/> // <MyEnumerablePropertyName/> // <MyEnumerablePropertyName/> // </MyObject> // // obtained via: // class MyObject // { // [XmlElement] // List<MyType> MyEnumerablePropertyName { get; set; } // } // // cf. https://docs.microsoft.com/en-us/dotnet/standard/serialization/controlling-xml-serialization-using-attributes //--------------------------------- Type itemsType = null; bool specialCaseWhereAnEnumerableHasTheXmlElementAttribute = (useXmlSerializerFormat && memberInformation.HasXmlElementAttribute && DataContractSerializer_Helpers.IsAssignableToGenericEnumerableOrArray(memberActualType, out itemsType)); if (specialCaseWhereAnEnumerableHasTheXmlElementAttribute) { object deserializedEnumerable = DeserializeToCSharpObject_Enumerable_WithRecursion_SpecialCase( memberInformation.Name, content, memberActualType, knownTypes, ignoreErrors, itemsType, useXmlSerializerFormat); memberValue = deserializedEnumerable; } //--------------------------------- // Set the value of the member: //--------------------------------- DataContractSerializer_Helpers.SetMemberValue(resultInstance, memberInformation, memberValue); membersForWhichWeSuccessfullSetTheValue.Add(memberInformation.Name); } } } else { //----------- // We ignore missing members, to mimic the behavior of the .NET DataContractSerializer. //----------- //throw new Exception("Member '" + memberName + "' not found in type '" + resultType.Name + "'."); } } } // In case of XmlSerializer compatibility mode, and [XmlAttribute] attribute on a class member, we also need to deserialize the XAttributes (cf. https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlattributeattribute(v=vs.110).aspx ): if (useXmlSerializerFormat) { foreach (XAttribute attribute in parentElement.Attributes()) { XName attributeName = attribute.Name; // We assume that the object properties have no namespace //todo: fix this assumption, cf. "XmlAttributeAttribute.Namespace" for example (note: repetition of "Attribute" is intended) if (string.IsNullOrEmpty(attributeName.NamespaceName)) { string attributeNameWithoutNamespace = attributeName.LocalName; // Find the member that has the name of the XAttribute: MemberInformation memberInformation; if (memberNameToMemberInformation.TryGetValue(attributeNameWithoutNamespace, out memberInformation) && memberInformation.HasXmlAttributeAttribute) { // Avoid processing members that have already been processed (just in case): if (!membersForWhichWeSuccessfullSetTheValue.Contains(memberInformation.Name)) { string attributeValue = attribute.Value; // Check to see if the expected type is a value type: if (DataContractSerializer_ValueTypesHandler.TypesToNames.ContainsKey(memberInformation.MemberType)) { // Attempt to deserialize the string: object memberValue = DataContractSerializer_ValueTypesHandler.ConvertStringToValueType(attributeValue, memberInformation.MemberType); // Set the value of the member: DataContractSerializer_Helpers.SetMemberValue(resultInstance, memberInformation, memberValue); membersForWhichWeSuccessfullSetTheValue.Add(memberInformation.Name); } else { //todo: report the error? if (memberInformation.MemberType == typeof(List <int>)) { string[] splittedElements = attributeValue.Split(' '); #if !BRIDGE List <int> listint = splittedElements.Select(Int32.Parse).ToList(); #else List <int> listint = new List <int>(); foreach (string str in splittedElements) { listint.Add(Int32.Parse(str)); } #endif DataContractSerializer_Helpers.SetMemberValue(resultInstance, memberInformation, listint); membersForWhichWeSuccessfullSetTheValue.Add(memberInformation.Name); } } } else { //todo: report the error? } } else { //todo: report the error? } } } } // Verify that the values of all the members marked as "IsRequired" have been set: foreach (var memberInformation in membersInformation) { if (memberInformation.IsRequired && !membersForWhichWeSuccessfullSetTheValue.Contains(memberInformation.Name)) { throw new SerializationException(string.Format("The member '{0}' is required but it was not found in the document being deserialized.", memberInformation.Name)); } } // Call the "OnDeserialized" method if any: CallOnDeserializedMethod(resultInstance, resultType); return(resultInstance); } else { return(null); } }