-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Reflection-based XmlSerializer - Deserialize empty collections and allow for sub-types in collection items. #111723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
fb695c7
28934e9
f0197fb
04226b8
100dca8
36093e6
af6124a
3e9f549
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Globalization; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Xml.Schema; | ||
|
@@ -122,7 +123,7 @@ private void WriteMember(object? o, object? choiceSource, ElementAccessor[] elem | |
} | ||
else | ||
{ | ||
WriteElements(o, elements, text, choice, writeAccessors, memberTypeDesc.IsNullable); | ||
WriteElements(o, choiceSource, elements, text, choice, writeAccessors, memberTypeDesc.IsNullable); | ||
} | ||
} | ||
|
||
|
@@ -146,10 +147,10 @@ private void WriteArray(object o, object? choiceSource, ElementAccessor[] elemen | |
} | ||
} | ||
|
||
WriteArrayItems(elements, text, choice, o); | ||
WriteArrayItems(elements, text, choice, o, choiceSource); | ||
} | ||
|
||
private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, ChoiceIdentifierAccessor? choice, object o) | ||
private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, ChoiceIdentifierAccessor? choice, object o, object? choiceSources) | ||
{ | ||
var arr = o as IList; | ||
|
||
|
@@ -158,7 +159,8 @@ private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, Cho | |
for (int i = 0; i < arr.Count; i++) | ||
{ | ||
object? ai = arr[i]; | ||
WriteElements(ai, elements, text, choice, true, true); | ||
var choiceSource = ((Array?)choiceSources)?.GetValue(i); | ||
WriteElements(ai, choiceSource, elements, text, choice, true, true); | ||
} | ||
} | ||
else | ||
|
@@ -169,16 +171,18 @@ private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, Cho | |
IEnumerator e = a.GetEnumerator(); | ||
if (e != null) | ||
{ | ||
int c = 0; | ||
while (e.MoveNext()) | ||
{ | ||
object ai = e.Current; | ||
WriteElements(ai, elements, text, choice, true, true); | ||
var choiceSource = ((Array?)choiceSources)?.GetValue(c++); | ||
WriteElements(ai, choiceSource, elements, text, choice, true, true); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void WriteElements(object? o, ElementAccessor[] elements, TextAccessor? text, ChoiceIdentifierAccessor? choice, bool writeAccessors, bool isNullable) | ||
private void WriteElements(object? o, object? choiceSource, ElementAccessor[] elements, TextAccessor? text, ChoiceIdentifierAccessor? choice, bool writeAccessors, bool isNullable) | ||
{ | ||
if (elements.Length == 0 && text == null) | ||
return; | ||
|
@@ -216,16 +220,35 @@ private void WriteElements(object? o, ElementAccessor[] elements, TextAccessor? | |
} | ||
else if (choice != null) | ||
{ | ||
if (o != null && o.GetType() == element.Mapping!.TypeDesc!.Type) | ||
// This looks heavy - getting names of enums in string form for comparison rather than just comparing values. | ||
// But this faithfully mimics NetFx, and is necessary to prevent confusion between different enum types. | ||
// ie EnumType.ValueX could == 1, but TotallyDifferentEnumType.ValueY could also == 1. | ||
TypeDesc td = element.Mapping!.TypeDesc!; | ||
bool enumUseReflection = choice.Mapping!.TypeDesc!.UseReflection; | ||
string enumTypeName = choice.Mapping!.TypeDesc!.FullName; | ||
string enumFullName = (enumUseReflection ? "" : enumTypeName + ".@") + FindChoiceEnumValue(element, (EnumMapping)choice.Mapping, enumUseReflection); | ||
string choiceFullName = (enumUseReflection ? "" : choiceSource!.GetType().FullName + ".@") + choiceSource!.ToString(); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you might have a problem here if the enum has the [Flags] attribute as ToString might return multiple names that are comma delimited. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a little trouble reasoning about this. Which is why I decided to not be clever and just do what ILGen does. This sequence here was pretty much modeled after the code here: https://github.com/StephenMolloy/runtime/blob/36093e63bbba5bbea9c970fb45adf336181aa86b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs#L3684 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, I don't think the 'enum' here is the property itself. Rather it refers to the list of enum values that are used to identify the type of an object in a choice list. Like is used in the 'choice' classes here: https://github.com/StephenMolloy/runtime/blob/36093e63bbba5bbea9c970fb45adf336181aa86b/src/libraries/System.Runtime.Serialization.Xml/tests/SerializationTypes.cs#L970-L1000 |
||
if (choiceFullName == enumFullName) | ||
{ | ||
WriteElement(o, element, writeAccessors); | ||
return; | ||
// Object is either non-null, or it is allowed to be null | ||
if (o != null || (!isNullable || element.IsNullable)) | ||
{ | ||
// But if Object is non-null, it's got to match types | ||
if (o != null && !td.Type!.IsAssignableFrom(o!.GetType())) | ||
{ | ||
throw CreateMismatchChoiceException(td.FullName, choice.MemberName!, enumFullName); | ||
} | ||
|
||
WriteElement(o, element, writeAccessors); | ||
return; | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
TypeDesc td = element.IsUnbounded ? element.Mapping!.TypeDesc!.CreateArrayTypeDesc() : element.Mapping!.TypeDesc!; | ||
if (o!.GetType() == td.Type) | ||
if (td.Type!.IsAssignableFrom(o!.GetType())) | ||
{ | ||
WriteElement(o, element, writeAccessors); | ||
return; | ||
|
@@ -274,6 +297,58 @@ private void WriteElements(object? o, ElementAccessor[] elements, TextAccessor? | |
} | ||
} | ||
|
||
private static string FindChoiceEnumValue(ElementAccessor element, EnumMapping choiceMapping, bool useReflection) | ||
{ | ||
string? enumValue = null; | ||
|
||
for (int i = 0; i < choiceMapping.Constants!.Length; i++) | ||
{ | ||
string xmlName = choiceMapping.Constants[i].XmlName; | ||
|
||
if (element.Any && element.Name.Length == 0) | ||
{ | ||
if (xmlName == "##any:") | ||
{ | ||
if (useReflection) | ||
enumValue = choiceMapping.Constants[i].Value.ToString(CultureInfo.InvariantCulture); | ||
else | ||
enumValue = choiceMapping.Constants[i].Name; | ||
break; | ||
} | ||
continue; | ||
} | ||
int colon = xmlName.LastIndexOf(':'); | ||
string? choiceNs = colon < 0 ? choiceMapping.Namespace : xmlName.Substring(0, colon); | ||
string choiceName = colon < 0 ? xmlName : xmlName.Substring(colon + 1); | ||
|
||
if (element.Name == choiceName) | ||
{ | ||
if ((element.Form == XmlSchemaForm.Unqualified && string.IsNullOrEmpty(choiceNs)) || element.Namespace == choiceNs) | ||
{ | ||
if (useReflection) | ||
enumValue = choiceMapping.Constants[i].Value.ToString(CultureInfo.InvariantCulture); | ||
else | ||
enumValue = choiceMapping.Constants[i].Name; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
if (string.IsNullOrEmpty(enumValue)) | ||
{ | ||
if (element.Any && element.Name.Length == 0) | ||
{ | ||
// Type {0} is missing enumeration value '##any' for XmlAnyElementAttribute. | ||
throw new InvalidOperationException(SR.Format(SR.XmlChoiceMissingAnyValue, choiceMapping.TypeDesc!.FullName)); | ||
} | ||
// Type {0} is missing value for '{1}'. | ||
throw new InvalidOperationException(SR.Format(SR.XmlChoiceMissingValue, choiceMapping.TypeDesc!.FullName, element.Namespace + ":" + element.Name, element.Name, element.Namespace)); | ||
} | ||
if (!useReflection) | ||
CodeIdentifier.CheckValidIdentifier(enumValue); | ||
return enumValue; | ||
} | ||
|
||
private void WriteText(object o, TextAccessor text) | ||
{ | ||
if (text.Mapping is PrimitiveMapping primitiveMapping) | ||
|
@@ -369,7 +444,7 @@ private void WriteElement(object? o, ElementAccessor element, bool writeAccessor | |
if (o != null) | ||
{ | ||
WriteStartElement(name, ns, false); | ||
WriteArrayItems(mapping.ElementsSortedByDerivation!, null, null, o); | ||
WriteArrayItems(mapping.ElementsSortedByDerivation!, null, null, o, null); | ||
WriteEndElement(); | ||
} | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.