IgnoreDataMember doesn't work, but JsonIgnore does
I suspect that the logic you are seeing is intended to make Json.NET consistent with DataContractJsonSerializer
in situations where a property is marked with both [DataContract]
and [IgnoreDataContract]
. When this is done, [DataContract]
will take precedence and the data contract serializer will output the property. E.g. serializing
[DataContract]
public class Test
{
[DataMember]
[IgnoreDataMember]
public virtual double information { get; set; }
}
Results in {"information":0}
.
(The logic may also be an efficiency tweak. In straightforward cases, if a type is marked with [DataContract]
, then [IgnoreDataMember]
is superfluous, so there's no need to spend time checking for it with reflection.)
Perhaps because of this, both Json.NET and DataContractJsonSerializer
will throw an exception serializing a derived class that overrides a data member property in its base class, marks the overridden property with [IgnoreDataMember]
, then adds an unrelated property with the same data member name. If you try to do this, Json.NET throws the exception you see -- and DataContractJsonSerializer
also throws an exception:
System.Runtime.Serialization.SerializationException occurred
Message="The data contract type 'Question38020614.ServerGeneratedObject' is not serializable with DataContractJsonSerializer because the data member 'information' is duplicated in its type hierarchy."
Source="System.ServiceModel.Web"
Nevertheless, you can make Json.NET behave as desired by creating a custom contract resolver inheriting from DefaultContractResolver
or CamelCasePropertyNamesContractResolver
, overriding CreateProperty()
and adding the desired logic:
public class IgnoreDataMemberContractResolver : DefaultContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static IgnoreDataMemberContractResolver instance;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static IgnoreDataMemberContractResolver() { instance = new IgnoreDataMemberContractResolver(); }
public static IgnoreDataMemberContractResolver Instance { get { return instance; } }
protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (memberSerialization == MemberSerialization.OptIn)
{
// Preserve behavior that [DataMember] supersedes [IgnoreDataMember] when applied in the same type
// but not when appled to a base type.
if (!property.Ignored
&& property.AttributeProvider.GetAttributes(typeof(IgnoreDataMemberAttribute), false).Any()
&& !property.AttributeProvider.GetAttributes(typeof(DataMemberAttribute), true).Any())
{
property.Ignored = true;
}
}
return property;
}
}
Then use it like:
var settings = new JsonSerializerSettings { ContractResolver = IgnoreDataMemberContractResolver.Instance };
var json = JsonConvert.SerializeObject(serverGeneratedObject, settings);
Effect of Order value in DataMember attribute during Json (De)Serialization
In general your conclusion about Json.NET is correct. The original JSON proposal states:
An object is an unordered set of name/value pairs.
And the Current IETF JSON standard states:
An object is an unordered collection of zero or more name/value pairs, where a name is a string and a value is a string, number, boolean, null, object, or array.
An object whose names are all unique is interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings.
Json.NET supports these standards. Thus, while it allows for applications to control property order via attributes when serializing (either through [JsonProperty(Order = X)]
; or through [DataMember(Order = X)]
, which works because Json.NET supports data contract attributes), when deserializing property order usually does not matter.
However, there are some exceptions. Those are:
Polymorpmic
"$type"
properties emitted whenTypeNameHandling
is enabled must appear first in the object. This limitation, however, can be removed by settingMetadataPropertyHandling.ReadAhead
. See here for details.Custom JsonConverters have access to the incoming token stream so can in theory behave differently depending on the order of properties.
For instance, when deserializing a
DataTable
, Json.NET's built-inDataTableConverter
will create columns in the order that they are encountered in the JSON file.It's also possible to write converters that assume a certain property order and thus are subtly broken. For instance, consider the following:
public struct Vector2D
{
public readonly double X;
public readonly double Y;
public Vector2D(double x, double y)
{
this.X = x;
this.Y = y;
}
}
public class Vector2DConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Vector2D);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException();
reader.Read(); // StartObject
reader.Read(); // X
var x = serializer.Deserialize<double>(reader);
reader.Read(); // Consume value
reader.Read(); // Y
var y = serializer.Deserialize<double>(reader);
reader.Read(); // Consume value
reader.Read(); // EndObject
return new Vector2D(x, y);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var vec = (Vector2D)value;
writer.WriteStartObject();
writer.WritePropertyName("X");
writer.WriteValue(vec.X);
writer.WritePropertyName("Y");
writer.WriteValue(vec.Y);
writer.WriteEndObject();
}
}The
Vector2DConverter
assumes that the propertiesX
andY
come in a certain order, rather than checking the property names and deserializing accordingly. While the converter can read what it writes, its order sensitivity fails to fully conform to the JSON standard. Users of such a converter might run into trouble e.g. if the JSON is temporarily cached in aDictionary<string, object>
before final conversion, since the order of elements in the c# dictionary is non-deterministic.None of Json.NET's built-in converters should be broken in this manner, but third-party converters might possibly be.
Update: Json.NET vs data contract serialization and member order
By default WCF uses DataContractSerializer
for XML and DataContractJsonSerializer
for JSON. DataContractSerializer
is sensitive to element order when deserializing. This cannot be disabled; see here, here or here.
DataContractJsonSerializer
on the other hand respects data member order when serializing but is insensitive to property order when deserializing as is required by the standard - with one exception. If the JSON contains a "type hint" to preserve polymorphic type identity when sending JSON for polymorphic objects over the wire, for instance:
{"__type":"Circle:#MyApp.Shapes","x":50,"y":70,"radius":10}
Then the "__type"
property must appear first in the object, as is stated in the docs:
Note that the type hint must appear first in the JSON representation. This is the only case where order of key/value pairs is important in JSON processing.
From experience I also have found that there must be no spacing between the {
and the "__type"
property name.
Json.NET also supports data contract attributes, as is explained in DataContract and DataMember Attributes. Its behavior with respect to property ordering is the same as DataContractJsonSerializer
(although it does use a different format for type hinting) in that it respects the ordering when serializing but is insensitive to ordering when deserializing.
Use DataMember during deserialize but nut during serialize?
You could define an interface for keeping these objects in sync...
public interface IData
{
string Filename { get; set; }
}
// deserialize me.
[DataContract]
public class IncomingData : IData
{
[DataMember(Name = "$Filename")]
public string Filename { get; set; }
}
// serialize me.
public class Data : IData
{
public string Filename { get; set; }
}
...or you could use virtual properties and override them with the serialization attributes...
// serialize me.
class Data
{
public virtual string Filename { get; set; }
}
// deserialize me.
[DataContract]
class IncomingData : Data
{
[DataMember(Name = "$Filename")]
public override string Filename { get => base.Filename; set => base.Filename = value; }
}
...both of these methods would require the use of a mapper like AutoMapper to clone the IncomingData
into the attribute-free Data
class...
mapper.Map<IncomingData, Data>(user);
...so I appreciate this feels less than ideal.
Why do DataContractJsonSerializer and Json.NET serialization of DateTimeOffset produce different json?
The JSON generated by DataContractJsonSerializer
for DateTimeOffset
and DateTime
is as documented. From Dates/Times and JSON:
DateTimeOffset is represented in JSON as a complex type:
{"DateTime":dateTime,"OffsetMinutes":offsetMinutes}
. TheoffsetMinutes
member is the local time offset from Greenwich Mean Time (GMT), also now referred to as Coordinated Universal Time (UTC), associated with the location of the event of interest. ThedateTime
member represents the instance in time when the event of interest occurred (again, it becomes a DateTime in JavaScript when ASP.NET AJAX is in use and a string when it is not). On serialization, the dateTime member is always serialized in GMT. So, if describing 3:00 AM New York time, dateTime has a time component of 8:00 AM and offsetMinutes are 300 (minus 300 minutes or 5 hours from GMT).Note
DateTime and DateTimeOffset objects, when serialized to JSON, only preserve information to millisecond precision. Sub-millisecond values (micro/nanoseconds) are lost during serialization.
And from DateTime Wire Format:
DateTime values appear as JSON strings in the form of
"/Date(700000+0500)/"
, where the first number (700000 in the example provided) is the number of milliseconds in the GMT time zone, regular (non-daylight savings) time since midnight, January 1, 1970. The number may be negative to represent earlier times. The part that consists of "+0500" in the example is optional and indicates that the time is of the Local kind - that is, should be converted to the local time zone on deserialization. If it is absent, the time is deserialized as Utc. The actual number ("0500" in this example) and its sign (+ or -) are ignored.
For Newtonsoft see the documentation page Serializing Dates in JSON for a discussion of how it serializes dates and times. By default ISO 8601 format strings are used but several formats are supported.
Now, it is possible to customize the data contract DateTime
format by setting DataContractJsonSerializerSettings.DateTimeFormat
:
var settings = new DataContractJsonSerializerSettings
{
DateTimeFormat = new DateTimeFormat("yyyy-MM-ddTHH\\:mm\\:ss.ffFFFFFzzz", CultureInfo.InvariantCulture)
{
},
};
DataContractJsonSerializer serializer = new DataContractJsonSerializer(item.GetType(), settings);
// Remainder as in your question.
However the result for DateTimeOffset
is as follows:
{"SaveDate":{"DateTime":"2020-06-04T22:00:00.00+00:00","OffsetMinutes":300}}
Which is not the simple string you seek. There doesn't seem to be any documented way to override the serialization format for DateTimeOffset
. Demo fiddle #1 here.
Since you wrote, The actual issue I am trying to solve is to get the data serialized by DataContractJsonSerializer to be deserialized by the JsonConvert DeserialzeObject method, it will be much easier to configure Json.NET to deserialize the DataContractJsonSerializer
format. First, define the following custom JsonConverter
:
public class DataContractDateTimeOffsetConverter : JsonConverter
{
readonly bool canWrite;
public DataContractDateTimeOffsetConverter() : this(true) { }
public DataContractDateTimeOffsetConverter(bool canWrite) => this.canWrite = canWrite;
public override bool CanWrite => canWrite;
public override bool CanConvert(Type objectType) => objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?);
[JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy))] // Ignore camel casing
class DateTimeOffsetDTO<TOffset> where TOffset : struct, IComparable, IFormattable
{
public DateTime DateTime { get; set; }
public TOffset OffsetMinutes { get; set; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var input = (DateTimeOffset)value;
var oldDateFormatHandling = writer.DateFormatHandling;
var oldDateTimeZoneHandling = writer.DateTimeZoneHandling;
try
{
writer.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
writer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
var offsetMinutes = input.Offset.TotalMinutes;
var offsetMinutesInt = checked((int)offsetMinutes);
var dateTime = input.DateTime.AddMinutes(-input.Offset.TotalMinutes);
if (offsetMinutesInt == offsetMinutes) // An integer number of mintues
serializer.Serialize(writer, new DateTimeOffsetDTO<int> { DateTime = dateTime, OffsetMinutes = offsetMinutesInt });
else
serializer.Serialize(writer, new DateTimeOffsetDTO<double> { DateTime = dateTime, OffsetMinutes = offsetMinutes });
}
finally
{
writer.DateFormatHandling = oldDateFormatHandling;
writer.DateTimeZoneHandling = oldDateTimeZoneHandling;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
switch (reader.MoveToContentAndAssert().TokenType)
{
// note that if there is a possibility of getting ISO 8601 strings for DateTimeOffset as well as complex objects, you may need to configure
// JsonSerializerSettings.DateParseHandling = DateParseHandling.None or DateParseHandling.DateTimeOffset at a higher code level to
// avoid premature deserialization as DateTime by JsonTextReader.
case JsonToken.String:
case JsonToken.Date:
return (DateTimeOffset)JToken.Load(reader);
case JsonToken.StartObject:
var old = reader.DateTimeZoneHandling;
try
{
reader.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
var dto = serializer.Deserialize<DateTimeOffsetDTO<double>>(reader);
var result = new DateTimeOffset(new DateTime(dto.DateTime.AddMinutes(dto.OffsetMinutes).Ticks, DateTimeKind.Unspecified),
TimeSpan.FromMinutes(dto.OffsetMinutes));
return result;
}
finally
{
reader.DateTimeZoneHandling = old;
}
case JsonToken.Null:
return null;
default:
throw new JsonSerializationException(); // Unknown token
}
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
Now you can deserialize the JSON generated by DataContractJsonSerializer
by adding the converter to JsonSerializerSettings.Converters
:
var settings = new JsonSerializerSettings
{
Converters = { new DataContractDateTimeOffsetConverter(true) },
};
var item = JsonConvert.DeserializeObject<TestToSeailize>(json, settings);
Notes:
If don't want to serialize in
DataContractJsonSerializer
format, passcanWrite : false
to the converter's constructor.If there is a possibility of getting ISO 8601 strings as well as complex objects for
DateTimeOffset
values, you may need to configureJsonSerializerSettings.DateParseHandling = DateParseHandling.None
orDateParseHandling.DateTimeOffset
at a higher code level to avoid premature deserialization of ISO 8601 strings asDateTime
objects byJsonTextReader
.
Demo fiddle #2 here.
How to ignore data in JSON.NET that changes between returns
I would explicitly specify exactly the properties I wanted serialized/deserialized in my data class using DataContractAttribute
and DataMemberAttribute
s for the members you actually want to deserialize.
This is opt in, so no attempt is made to shoehorn anything extra in your JSON into your data class, and anything extra in your data class doesn't show up in serialized JSON.
So assuming your class right now looks like this:
class MyData {
// Old member
// public Translations Translations { get; set; }
public List<Translation> Translations { get; set; }
public string ThisShouldBeSerialized { get; set; }
}
You can change it so things that you want serialized are explicitly marked as such, and anything not marked for serialization is ignored by default:
[DataContract]
class MyData {
// Old member
// public Translations Translations { get; set; }
public List<Translation> Translations { get; set; }
[DataMember]
public string ThisShouldBeSerialized { get; set; }
}
And then:
var myJSON = @"{
'ThisShouldBeSerialized': 'test',
'Translations': {
'Some' : 'Strange',
'Data' : 'Blob'
}
}";
var result = JsonConvert.DeserializeObject<MyData>(myJSON);
Related Topics
Easiest Way to Read from a Url into a String in .Net
Most Efficient Way to Insert Rows into MySQL Database
Linq to Entities, Random Order
Jtoken: Get Raw/Original JSON Value
What Really Happens in a Try { Return X; } Finally { X = Null; } Statement
Pass Parameter to Eventhandler
Verifying Jwt Signed with the Rs256 Algorithm Using Public Key in C#
How to Run a Task on a Custom Taskscheduler Using Await
What Is an MVChtmlstring and When Should I Use It
How to Get the File Size in C#
Using the Null-Conditional Operator on the Left-Hand Side of an Assignment
Windows.Ui.Notifications Is Missing
Very Slow Compile Times on Visual Studio 2005
Filter/Search Using Multiple Fields - ASP.NET MVC