ASP.NET WebApi 2: Disable deserialization of string argument containing ISO8601 Date

166 views Asked by At

I have an Asp.Net WebApi controller with the following action:

public void Post([FromBody]object value)

Now, sometimes this value parameter is sent as a String containing a ISO8601-formatted date, and sometimes as a DateTime. The data is sent in JSON format.

For each of those two options I have to do different things, so I need to make that distinction, but I always end up with a DateTime value.

I am aware of the fact that my source DateTime values are being serialized to JSON as strings in ISO8601 format when they are sent to the above action (I am using an HttpClient for the actual sending), and hence my action cannot differentiate between the two.

My question is whether it is possible to include some kind of type hint to the sent values, in order to tell my WebApi endpoint which specific type is being sent (and enforce that type on the deserialized value).

1

There are 1 answers

0
Johan Hirsch On BEST ANSWER

Solved this eventually by implementing a custom JsonConverter, which serializes string values along with a type hint (similar to Newtonsoft.Json's MetadataPropertyHandling setting in JsonSerializerSettings).

Here is the implementation which does the trick:

public class DateStringTypeConservingConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsAssignableFrom(typeof(string));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JToken.ReadFrom(reader);
        var typeMetadata = token["$type"];

        if (typeMetadata?.Value<string>() == typeof(string).FullName)
        {
            return token.Value<string>("$value");
        }

        // Default behavior
        return serializer.Deserialize(reader, objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value is string)
        {
            WriteJsonStringWithType(writer, value as string);
        }
        else
        {
            // Default behavior
            serializer.Serialize(writer, value);
        }
    }

    // Write the given string to the given JSON writer with type info:
    // { $type: "System.String", $value: "<string content>" }
    private void WriteJsonStringWithType(JsonWriter writer, string value)
    {
        writer.WriteStartObject();

        writer.WritePropertyName("$type");
        writer.WriteValue(typeof(string).FullName);

        writer.WritePropertyName("$value");
        writer.WriteValue(value);

        writer.WriteEndObject();
    }
}

And a little usage example:

    static void Main(string[] args)
    {
        var dateString = new Wrapper
        {
            Value = "2017-01-08T21:24:48.114Z"
        };

        var converters = new JsonConverter[] { new DateStringTypeConservingConverter() };
        var serializedDateString = JsonConvert.SerializeObject(dateString, new JsonSerializerSettings
        {
            Converters = converters
        });

        var deserializedDateString = 
            JsonConvert.DeserializeObject<Wrapper>(serializedDateString, converters);

        // Output: System.String
        Console.WriteLine($"The type of deserialized value is: { deserializedDateString.Value.GetType() }");

        Console.ReadKey();
    }

    class Wrapper
    {
        public object Value { get; set; }
    }