-
Notifications
You must be signed in to change notification settings - Fork 5.1k
XmlSerializer dont skip date time offset #55101
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 all commits
93e2374
b573c46
4ba6726
c902309
7becea3
6227ea2
e2e254c
e7b34cd
fa5b2cc
b229d29
caabfe0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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(); | ||
|
@@ -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) | ||
{ | ||
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 see you copied the pattern from TimeSpan. Do you know why these have an extra Reader.IsEmptyElement check? Is it to avoid breaking existing users where you are consuming from a version which output null? 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. As you noted, this method was copied from the same pattern used to handle TimeSpan. So I'm just guessing here. But it is worth noting that prior to this PR, XmlSerializer would emit xml elements for DateTimeOffset fields, but they would be empty. Without these check, we would start to fail noisily when updated readers see those empty elements, whereas previously those members were simply being restored with default values. 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. This is non-obvious. We need to do something about this. Either through documentation, or by letting it fail and have an AppContext switch to add this check. I'll let you decide which as it can be argued both ways whether we should be breaking people to alert them that they aren't getting the data they expect vs not introducing breaks and it's a philosophical more than technical question. But this behavior needs to be discoverable either through an exception or documentation. I'm going to approve this based on the presumption you will go the documentation route. |
||
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; | ||
|
@@ -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!; | ||
|
@@ -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"); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments seem a little redundant. Besides that, I don't understand what's going on here. If looks like it has an object in
o
of type DateTimeOffset, and it's emitting the IL to place this concrete value onto the stack and then create a new DateTimeOffset from it. But this doesn't make sense as we don't emit IL every time we serialize. So where does the DataTimeOffset object ino
come from, and why do we statically emit IL for this single instance?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DefaultValueAttribute I believe. WritePrimitive->WriteCheckDefault->this code. When we're cobbling together the IL writer, if a serializable member has a default value attribute, then we need to add code to check against the default value, which may or may not be 'default(DateTimeOffset)'. This code gets hit for each unique default value. Ie, if all your DateTimeOffset fields have 12/31/1999 as their default, then we only hit this once to check against that one default value. But if one of your DateTimeOffset fields has 1/1/2000 as it's default unlike the others, then we hit this code again to check the default for that field.