Skip to content

Commit

Permalink
XmlSerializer dont skip date time offset (#55101)
Browse files Browse the repository at this point in the history
* Add DateTimeOffset as a primitive for XmlSerializer.

* Fix DefaultValueAttribute handling for DTO.

* Add tests for DateTimeAttribute and XmlSerializer.

* Missed a 'KnownType' spot.

* Update SGen tests to use current dotnet.exe from build.

* Unbreak runtimeconfig creation for testing.

* Debugging failure to resolve CoreCLR path.

* Test fixup.

* More test cleanup.

* Temporarily disable generator tests.

* Use InitObj instead of constructors for DateTimeOffset and TimeSpan.
  • Loading branch information
StephenMolloy authored Jul 15, 2021
1 parent b1452c8 commit 19b1b48
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<DefineConstants>$(DefineConstants);XMLSERIALIZERGENERATORTESTS</DefineConstants>
<TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
<SkipTestsOnPlatform Condition="'$(TargetsMobile)' == 'true' or '$(TargetOS)' == 'FreeBSD' or '$(TargetArchitecture)' == 'arm' or '$(TargetArchitecture)' == 'arm64' or '$(TargetArchitecture)' == 'armel' or '$(TargetArchitecture)' == 'wasm'">true</SkipTestsOnPlatform>
<SkipTestsOnPlatform>true</SkipTestsOnPlatform>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,19 @@ internal void Ldc(object o)
New(TimeSpan_ctor);
break;
}
else if (valueType == typeof(DateTimeOffset))
{
ConstructorInfo DateTimeOffset_ctor = typeof(DateTimeOffset).GetConstructor(
CodeGenerator.InstanceBindingFlags,
null,
new Type[] { typeof(long), typeof(TimeSpan) },
null
)!;
Ldc(((DateTimeOffset)o).Ticks); // ticks
Ldc(((DateTimeOffset)o).Offset); // offset
New(DateTimeOffset_ctor);
break;
}
else
{
throw new NotSupportedException(SR.Format(SR.UnknownConstantType, valueType.AssemblyQualifiedName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ internal void Write_dateTime(object? o)
WriteElementStringRaw(@"dateTime", @"", FromDateTime(((System.DateTime)o)));
}

internal void Write_dateTimeOffset(object? o)
{
WriteStartDocument();
if (o == null)
{
WriteEmptyTag(@"dateTimeOffset", @"");
return;
}
DateTimeOffset dto = (DateTimeOffset)o;
WriteElementStringRaw(@"dateTimeOffset", @"", System.Xml.XmlConvert.ToString(dto));
}

internal void Write_unsignedByte(object? o)
{
WriteStartDocument();
Expand Down Expand Up @@ -454,6 +466,36 @@ internal sealed class XmlSerializationPrimitiveReader : System.Xml.Serialization
return (object?)o;
}

internal object? Read_dateTimeOffset()
{
object? o = null;
Reader.MoveToContent();
if (Reader.NodeType == System.Xml.XmlNodeType.Element)
{
if (((object)Reader.LocalName == (object)_id20_dateTimeOffset && (object)Reader.NamespaceURI == (object)_id2_Item))
{
if (Reader.IsEmptyElement)
{
Reader.Skip();
o = default(DateTimeOffset);
}
else
{
o = System.Xml.XmlConvert.ToDateTimeOffset(Reader.ReadElementString());
}
}
else
{
throw CreateUnknownNodeException();
}
}
else
{
UnknownNode(null);
}
return (object?)o;
}

internal object? Read_unsignedByte()
{
object? o = null;
Expand Down Expand Up @@ -720,6 +762,7 @@ protected override void InitCallbacks()
private string _id15_unsignedLong = null!;
private string _id7_float = null!;
private string _id10_dateTime = null!;
private string _id20_dateTimeOffset = null!;
private string _id6_long = null!;
private string _id9_decimal = null!;
private string _id8_double = null!;
Expand All @@ -743,6 +786,7 @@ protected override void InitIDs()
_id15_unsignedLong = Reader.NameTable.Add(@"unsignedLong");
_id7_float = Reader.NameTable.Add(@"float");
_id10_dateTime = Reader.NameTable.Add(@"dateTime");
_id20_dateTimeOffset = Reader.NameTable.Add(@"dateTimeOffset");
_id6_long = Reader.NameTable.Add(@"long");
_id9_decimal = Reader.NameTable.Add(@"decimal");
_id8_double = Reader.NameTable.Add(@"double");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,11 @@ private void WriteMemberElementsIf(Member[] expectedMembers, Member? anyElementM
Reader.Skip();
value = default(TimeSpan);
}
else if (element.Mapping.TypeDesc!.Type == typeof(DateTimeOffset) && Reader.IsEmptyElement)
{
Reader.Skip();
value = default(DateTimeOffset);
}
else
{
if (element.Mapping.TypeDesc == QnameTypeDesc)
Expand Down Expand Up @@ -1219,6 +1224,7 @@ private object WritePrimitive(TypeMapping mapping, Func<object, string> readFunc
"Guid" => XmlConvert.ToGuid(value),
"Char" => XmlConvert.ToChar(value),
"TimeSpan" => XmlConvert.ToTimeSpan(value),
"DateTimeOffset" => XmlConvert.ToDateTimeOffset(value),
_ => throw new InvalidOperationException(SR.Format(SR.XmlInternalErrorDetails, $"unknown FormatterName: {mapping.TypeDesc.FormatterName}")),
};
return retObj;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,7 @@ private string ConvertPrimitiveToString(object o, TypeDesc typeDesc)
"Guid" => XmlConvert.ToString((Guid)o),
"Char" => XmlConvert.ToString((char)o),
"TimeSpan" => XmlConvert.ToString((TimeSpan)o),
"DateTimeOffset" => XmlConvert.ToString((DateTimeOffset)o),
_ => o.ToString()!,
};
return stringValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ static TypeScope()
AddNonXsdPrimitive(typeof(Guid), "guid", UrtTypes.Namespace, "Guid", new XmlQualifiedName("string", XmlSchema.Namespace), new XmlSchemaFacet[] { guidPattern }, TypeFlags.CanBeAttributeValue | TypeFlags.CanBeElementValue | TypeFlags.XmlEncodingNotRequired | TypeFlags.IgnoreDefault);
AddNonXsdPrimitive(typeof(char), "char", UrtTypes.Namespace, "Char", new XmlQualifiedName("unsignedShort", XmlSchema.Namespace), Array.Empty<XmlSchemaFacet>(), TypeFlags.CanBeAttributeValue | TypeFlags.CanBeElementValue | TypeFlags.HasCustomFormatter | TypeFlags.IgnoreDefault);
AddNonXsdPrimitive(typeof(TimeSpan), "TimeSpan", UrtTypes.Namespace, "TimeSpan", new XmlQualifiedName("duration", XmlSchema.Namespace), Array.Empty<XmlSchemaFacet>(), TypeFlags.CanBeAttributeValue | TypeFlags.CanBeElementValue | TypeFlags.XmlEncodingNotRequired);
AddNonXsdPrimitive(typeof(DateTimeOffset), "dateTimeOffset", UrtTypes.Namespace, "DateTimeOffset", new XmlQualifiedName("dateTime", XmlSchema.Namespace), Array.Empty<XmlSchemaFacet>(), TypeFlags.CanBeAttributeValue | TypeFlags.CanBeElementValue | TypeFlags.XmlEncodingNotRequired);

AddSoapEncodedTypes(Soap.Encoding);

Expand Down Expand Up @@ -596,6 +597,8 @@ internal static bool IsKnownType(Type type)
return true;
else if (type == typeof(TimeSpan))
return true;
else if (type == typeof(DateTimeOffset))
return true;
else if (type == typeof(XmlNode[]))
return true;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public abstract class XmlSerializationReader : XmlSerializationGeneratedCode
private string _charID = null!;
private string _guidID = null!;
private string _timeSpanID = null!;
private string _dateTimeOffsetID = null!;

protected abstract void InitIDs();

Expand Down Expand Up @@ -214,6 +215,7 @@ private void InitPrimitiveIDs()
_charID = _r.NameTable.Add("char");
_guidID = _r.NameTable.Add("guid");
_timeSpanID = _r.NameTable.Add("TimeSpan");
_dateTimeOffsetID = _r.NameTable.Add("dateTimeOffset");
_base64ID = _r.NameTable.Add("base64");

_anyURIID = _r.NameTable.Add("anyURI");
Expand Down Expand Up @@ -667,6 +669,8 @@ private byte[] ReadByteArray(bool isBase64)
value = new Guid(CollapseWhitespace(ReadStringValue()));
else if ((object)type.Name == (object)_timeSpanID)
value = XmlConvert.ToTimeSpan(ReadStringValue());
else if ((object)type.Name == (object)_dateTimeOffsetID)
value = XmlConvert.ToDateTimeOffset(ReadStringValue());
else
value = ReadXmlNodes(elementCanBeType);
}
Expand Down Expand Up @@ -764,6 +768,8 @@ private byte[] ReadByteArray(bool isBase64)
value = default(Nullable<Guid>);
else if ((object)type.Name == (object)_timeSpanID)
value = default(Nullable<TimeSpan>);
else if ((object)type.Name == (object)_dateTimeOffsetID)
value = default(Nullable<DateTimeOffset>);
else
value = null;
}
Expand Down Expand Up @@ -4700,13 +4706,20 @@ private void WriteElement(string source, string? arrayName, string? choiceSource
}
Writer.Indent++;

if (element.Mapping.TypeDesc!.Type == typeof(TimeSpan))
if (element.Mapping.TypeDesc!.Type == typeof(TimeSpan) || element.Mapping.TypeDesc!.Type == typeof(DateTimeOffset))
{
Writer.WriteLine("if (Reader.IsEmptyElement) {");
Writer.Indent++;
Writer.WriteLine("Reader.Skip();");
WriteSourceBegin(source);
Writer.Write("default(System.TimeSpan)");
if (element.Mapping.TypeDesc!.Type == typeof(TimeSpan))
{
Writer.Write("default(System.TimeSpan)");
}
else if (element.Mapping.TypeDesc!.Type == typeof(DateTimeOffset))
{
Writer.Write("default(System.DateTimeOffset)");
}
WriteSourceEnd(source);
Writer.WriteLine(";");
Writer.Indent--;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3096,7 +3096,7 @@ private void WriteElement(string source, string? arrayName, string? choiceSource
{
}

if (element.Mapping.TypeDesc!.Type == typeof(TimeSpan))
if ((element.Mapping.TypeDesc!.Type == typeof(TimeSpan)) || element.Mapping.TypeDesc!.Type == typeof(DateTimeOffset))
{
MethodInfo XmlSerializationReader_get_Reader = typeof(XmlSerializationReader).GetMethod(
"get_Reader",
Expand All @@ -3121,14 +3121,10 @@ private void WriteElement(string source, string? arrayName, string? choiceSource
ilg.Ldarg(0);
ilg.Call(XmlSerializationReader_get_Reader);
ilg.Call(XmlReader_Skip);
ConstructorInfo TimeSpan_ctor = typeof(TimeSpan).GetConstructor(
CodeGenerator.InstanceBindingFlags,
null,
new Type[] { typeof(long) },
null
)!;
ilg.Ldc(default(TimeSpan).Ticks);
ilg.New(TimeSpan_ctor);
LocalBuilder tmpLoc = ilg.GetTempLocal(element.Mapping.TypeDesc!.Type);
ilg.Ldloca(tmpLoc);
ilg.InitObj(element.Mapping.TypeDesc!.Type);
ilg.Ldloc(tmpLoc);
WriteSourceEnd(source, element.Mapping.TypeDesc.Type);
ilg.Else();
WriteSourceBegin(source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ private XmlQualifiedName GetPrimitiveTypeName(Type type)
typeName = "TimeSpan";
typeNs = UrtTypes.Namespace;
}
else if (type == typeof(DateTimeOffset))
{
typeName = "dateTimeOffset";
typeNs = UrtTypes.Namespace;
}
else if (type == typeof(XmlNode[]))
{
typeName = Soap.UrType;
Expand Down Expand Up @@ -345,6 +350,12 @@ protected void WriteTypedPrimitive(string? name, string? ns, object o, bool xsiT
type = "TimeSpan";
typeNs = UrtTypes.Namespace;
}
else if (t == typeof(DateTimeOffset))
{
value = XmlConvert.ToString((DateTimeOffset)o);
type = "dateTimeOffset";
typeNs = UrtTypes.Namespace;
}
else if (typeof(XmlNode[]).IsAssignableFrom(t))
{
if (name == null)
Expand Down Expand Up @@ -4321,6 +4332,18 @@ private void WriteValue(object value)
Writer.Write(((DateTime)value).Ticks.ToString(CultureInfo.InvariantCulture));
Writer.Write(")");
}
else if (type == typeof(DateTimeOffset))
{
Writer.Write(" new ");
Writer.Write(type.FullName);
Writer.Write("(");
Writer.Write(((DateTimeOffset)value).Ticks.ToString(CultureInfo.InvariantCulture));
Writer.Write(", new ");
Writer.Write(((DateTimeOffset)value).Offset.GetType().FullName);
Writer.Write("(");
Writer.Write(((DateTimeOffset)value).Offset.Ticks.ToString(CultureInfo.InvariantCulture));
Writer.Write("))");
}
else if (type == typeof(TimeSpan))
{
Writer.Write(" new ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,10 @@ private void SerializePrimitive(XmlWriter xmlWriter, object? o, XmlSerializerNam
{
writer.Write_TimeSpan(o);
}
else if (_primitiveType == typeof(DateTimeOffset))
{
writer.Write_dateTimeOffset(o);
}
else
{
throw new InvalidOperationException(SR.Format(SR.XmlUnxpectedType, _primitiveType!.FullName));
Expand Down Expand Up @@ -971,6 +975,10 @@ private void SerializePrimitive(XmlWriter xmlWriter, object? o, XmlSerializerNam
{
o = reader.Read_TimeSpan();
}
else if (_primitiveType == typeof(DateTimeOffset))
{
o = reader.Read_dateTimeOffset();
}
else
{
throw new InvalidOperationException(SR.Format(SR.XmlUnxpectedType, _primitiveType!.FullName));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,84 @@ public static void Xml_DeserializeEmptyTimeSpanType()
}
}

[Fact]
public static void Xml_TypeWithDateTimeOffsetProperty()
{
var now = new DateTimeOffset(DateTime.Now);
var defDTO = default(DateTimeOffset);
var obj = new TypeWithDateTimeOffsetProperties { DTO = now };
var deserializedObj = SerializeAndDeserialize(obj,
@"<?xml version=""1.0""?>
<TypeWithDateTimeOffsetProperties xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<DTO>" + XmlConvert.ToString(now) + @"</DTO>
<DTO2>" + XmlConvert.ToString(defDTO) + @"</DTO2>
<NullableDTO xsi:nil=""true"" />
<NullableDefaultDTO xsi:nil=""true"" />
</TypeWithDateTimeOffsetProperties>");
Assert.StrictEqual(obj.DTO, deserializedObj.DTO);
Assert.StrictEqual(obj.DTO2, deserializedObj.DTO2);
Assert.StrictEqual(defDTO, deserializedObj.DTO2);
Assert.StrictEqual(obj.DTOWithDefault, deserializedObj.DTOWithDefault);
Assert.StrictEqual(defDTO, deserializedObj.DTOWithDefault);
Assert.StrictEqual(obj.NullableDTO, deserializedObj.NullableDTO);
Assert.True(deserializedObj.NullableDTO == null);
Assert.StrictEqual(obj.NullableDTOWithDefault, deserializedObj.NullableDTOWithDefault);
Assert.True(deserializedObj.NullableDTOWithDefault == null);
}

[Fact]
public static void Xml_DeserializeTypeWithEmptyDateTimeOffsetProperties()
{
//var def = DateTimeOffset.Parse("3/17/1977 5:00:01 PM -05:00"); // "1977-03-17T17:00:01-05:00"
var defDTO = default(DateTimeOffset);
string xml = @"<?xml version=""1.0""?>
<TypeWithDateTimeOffsetProperties xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<DTO />
<DTO2 />
<DTOWithDefault />
<NullableDefaultDTO />
</TypeWithDateTimeOffsetProperties>";
XmlSerializer serializer = new XmlSerializer(typeof(TypeWithDateTimeOffsetProperties));

using (StringReader reader = new StringReader(xml))
{
TypeWithDateTimeOffsetProperties deserializedObj = (TypeWithDateTimeOffsetProperties)serializer.Deserialize(reader);
Assert.NotNull(deserializedObj);
Assert.Equal(defDTO, deserializedObj.DTO);
Assert.Equal(defDTO, deserializedObj.DTO2);
Assert.Equal(defDTO, deserializedObj.DTOWithDefault);
Assert.True(deserializedObj.NullableDTO == null);
Assert.Equal(defDTO, deserializedObj.NullableDTOWithDefault);
}
}

[Fact]
public static void Xml_DeserializeDateTimeOffsetType()
{
var now = new DateTimeOffset(DateTime.Now);
string xml = @"<?xml version=""1.0""?><dateTimeOffset>" + now.ToString("o") + "</dateTimeOffset>";
XmlSerializer serializer = new XmlSerializer(typeof(DateTimeOffset));

using (StringReader reader = new StringReader(xml))
{
DateTimeOffset deserializedObj = (DateTimeOffset)serializer.Deserialize(reader);
Assert.Equal(now, deserializedObj);
}
}

[Fact]
public static void Xml_DeserializeEmptyDateTimeOffsetType()
{
string xml = @"<?xml version=""1.0""?><dateTimeOffset />";
XmlSerializer serializer = new XmlSerializer(typeof(DateTimeOffset));

using (StringReader reader = new StringReader(xml))
{
DateTimeOffset deserializedObj = (DateTimeOffset)serializer.Deserialize(reader);
Assert.Equal(default(DateTimeOffset), deserializedObj);
}
}

[Fact]
public static void Xml_TypeWithByteProperty()
{
Expand Down
Loading

0 comments on commit 19b1b48

Please sign in to comment.