Why Json.net does not use customized IsoDateTimeConverter?

时间秒杀一切 提交于 2019-12-18 09:09:07

问题


I use Json.net to serialize my objects and I want to customize DateTime output:

Here is a small example:

[DataContract]
class x
{
    [DataMember]
    [JsonConverter(typeof(IsoDateTimeConverter))]
    public DateTime datum = new DateTime(1232, 3, 23);
}

var dtc = new IsoDateTimeConverter();
dtc.DateTimeFormat = "yy";
JsonConvert.SerializeObject(new x(), dtc);

The result is {"datum":"1232-03-23T00:00:00"} instead of {"datum":"1232"}.

This works correctly (returning "32"):

return JsonConvert.SerializeObject(new DateTime(1232, 3, 23), dtc);

Where is the catch?


回答1:


The catch is that the converter applied via [JsonConverter(typeof(IsoDateTimeConverter))] supersedes the converter passed into the serializer. This is documented in Serialization Attributes: JsonConverterAttribute:

The JsonConverterAttribute specifies which JsonConverter is used to convert an object.

The attribute can be placed on a class or a member. When placed on a class, the JsonConverter specified by the attribute will be the default way of serializing that class. When the attribute is on a field or property, then the specified JsonConverter will always be used to serialize that value.

The priority of which JsonConverter is used is member attribute, then class attribute, and finally any converters passed to the JsonSerializer.

As a workaround, in the ReadJson() and WriteJson() methods of the applied converter, one could check for a relevant converter in the serializer's list of converters, and if one is found, use it. The decorator pattern can be used to separate this logic from the underlying conversion logic. First, introduce:

public class OverridableJsonConverterDecorator : JsonConverterDecorator
{
    public OverridableJsonConverterDecorator(Type jsonConverterType) : base(jsonConverterType) { }

    public OverridableJsonConverterDecorator(JsonConverter converter) : base(converter) { }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        foreach (var converter in serializer.Converters)
        {
            if (converter == this)
            {
                Debug.WriteLine("Skipping identical " + converter.ToString());
                continue;
            }
            if (converter.CanConvert(value.GetType()) && converter.CanWrite)
            {
                converter.WriteJson(writer, value, serializer);
                return;
            }
        }
        base.WriteJson(writer, value, serializer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        foreach (var converter in serializer.Converters)
        {
            if (converter == this)
            {
                Debug.WriteLine("Skipping identical " + converter.ToString());
                continue;
            }
            if (converter.CanConvert(objectType) && converter.CanRead)
            {
                return converter.ReadJson(reader, objectType, existingValue, serializer);
            }
        }
        return base.ReadJson(reader, objectType, existingValue, serializer);
    }
}

public abstract class JsonConverterDecorator : JsonConverter
{
    readonly JsonConverter converter;

    public JsonConverterDecorator(Type jsonConverterType) : this((JsonConverter)Activator.CreateInstance(jsonConverterType)) { }

    public JsonConverterDecorator(JsonConverter converter)
    {
        if (converter == null)
            throw new ArgumentNullException();
        this.converter = converter;
    }

    public override bool CanConvert(Type objectType)
    {
        return converter.CanConvert(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return converter.ReadJson(reader, objectType, existingValue, serializer);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        converter.WriteJson(writer, value, serializer);
    }

    public override bool CanRead { get { return converter.CanRead; } }

    public override bool CanWrite { get { return converter.CanWrite; } }
}

Then, apply the decorator on top of IsoDateTimeConverter as follows:

[DataContract]
class x
{
    [DataMember]
    [JsonConverter(typeof(OverridableJsonConverterDecorator), typeof(IsoDateTimeConverter))]
    public DateTime datum = new DateTime(1232, 3, 23);
}

Now the statically applied converter will be superseded as required. Sample fiddle.

Note that, for this specific test case, as of Json.NET 4.5.1 dates are serialized in ISO by default and IsoDateTimeConverter is no longer required. Forcing dates to be serialized in a specific format can be accomplished by setting JsonSerializerSettings.DateFormatString:

[DataContract]
class x
{
    [DataMember]
    public DateTime datum = new DateTime(1232, 3, 23);
}

var settings = new JsonSerializerSettings { DateFormatString = "yy" };
var json1 = JsonConvert.SerializeObject(new x(), settings);
Console.WriteLine(json1); // Prints {"datum":"32"}

var json2 = JsonConvert.SerializeObject(new x());
Console.WriteLine(json2); // Prints {"datum":"1232-03-23T00:00:00"}

Sample fiddle. Nevertheless the general question deserves an answer.



来源:https://stackoverflow.com/questions/6384132/why-json-net-does-not-use-customized-isodatetimeconverter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!