Configure JSON.Net to Ignore Datacontract/Datamember Attributes

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:

  1. Polymorpmic "$type" properties emitted when TypeNameHandling is enabled must appear first in the object. This limitation, however, can be removed by setting MetadataPropertyHandling.ReadAhead. See here for details.

  2. 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-in DataTableConverter 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 properties X and Y 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 a Dictionary<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}. The offsetMinutes 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. The dateTime 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, pass canWrite : 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 configure JsonSerializerSettings.DateParseHandling = DateParseHandling.None or DateParseHandling.DateTimeOffset at a higher code level to avoid premature deserialization of ISO 8601 strings as DateTime objects by JsonTextReader.

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 DataMemberAttributes 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



Leave a reply



Submit