1
votes

I have an object with the following property (it has miliseconds since 1970):

public double AsOfDate { get; set; }

When I serialize it (in ASP .NET MVC4 application) it's fine. However when I later send back the JSON object to the server, I get the deserialization error:

Error reading date. Unexpected token: Integer. Path 'performance.asOfDate', line 1, position 95.

And stack trace:

at Newtonsoft.Json.JsonReader.ReadAsDateTimeInternal() at Newtonsoft.Json.JsonTextReader.ReadAsDateTime() at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolvePropertyAndConstructorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectFromNonDefaultConstructor(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ConstructorInfo constructorInfo, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultConstructor) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolvePropertyAndConstructorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectFromNonDefaultConstructor(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ConstructorInfo constructorInfo, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultConstructor) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)

So it seems that during deserialization phase, JSON library is trying to parse asOfDate as DateTime only because it has Date string in the name? Is there a way to resolve this issue? Here's sample request body:

{
   "performance":{
      "performanceSinceInception":0.76,
      "performanceMtd":0.41,
      "asOf":1382486400000,
      "inceptionDate":1359676800000,
      "monthPerformanceStart":1380585600000
   },       
   "rating":1,
   "isActive":false,
   "effectiveFrom":1359676800000,
   "effectiveTo":1383919343000,
   "globalValidationFlag":true,
   "whitelistValidationFlag":false,
   "canBeReactivated":false,
   "canBeRemoved":false,
   "belongsToMainBucket":true
}

I've tested it, and when I change property name from asOfDate to asOf it works. So I blame convention based deserialization. Is there a way to override it ?

1
Can you post a sample of the JSON in question?Taylan Aydinli

1 Answers

3
votes

I think the problem is that your classes do not have parameterless (default) constructors. (I can tell this is the case because in the stack trace you posted, it shows that CreateObjectFromNonDefaultConstructor is getting called.)

When a class does not have a default constructor, Json.Net does some funky things to try to figure out which constructor, of the ones available, it can use to create the object, and how to match up the constructor parameters with the JSON data in order to call it. That involves a certain amount of guesswork, and it doesn't always work.

One case in particular where it won't work correctly is if you have a constructor with a parameter name matching a property in the JSON data, but the data types are incompatible. For example, consider the following program:

class Program
{
    static void Main(string[] args)
    {
        string json = @"{ ""asOf"" : 1382486400000 }";

        var obj = JsonConvert.DeserializeObject<Performance>(json);
        Console.WriteLine(obj.asOf);
    }

    public class Performance
    {
        public Performance(DateTime asOf)
        {
            this.asOf = asOf.Ticks;
        }

        public double asOf { get; set; }
    }
}

This program will fail with the same error as in your question. There is no default constructor, so Json.Net tries to use the one that is there. There is a parameter asOf in the constructor, and it so happens there is also a property asOf in the data. So it tries to use that to construct the object. But, this won't work because the data is an integer, not something that Json.Net knows how to turn into a date.

There are a couple of possible solutions. The best solution is simply to add a default constructor. If there is a public default constructor, Json.Net will always prefer it over any other. With this change, Json.Net will use the default constructor to create the object, and then match the asOf property in the JSON data with the asOf property in the object, whose types are compatible. So this works fine.

Another possible solution is to change the name of the constructor parameter so that it doesn't match the JSON data (or conversely, change the name of object property, both in the class and the JSON data, so that they don't match the constructor). In this case, Json.Net is forced to use a default value to pass to the constructor to create the object. Then, it will match up the JSON data with any remaining object properties. For our example, this also works. Of course, relying on this behavior is a little risky, because Json.Net has no idea what logic might be in the constructor. For example, you could have code which rejects default values (e.g. null) as being invalid for certain parameters. In that case, Json.Net would not be able to construct the object. It can only do the best it can with what you give it, and that may not always work as expected.

The third solution is to create a custom JsonConverter for your object and have Json.Net use that to construct and populate your object. If you have some complicated object that can't have a default constructor, but requires special handling to populate it, this is your best bet rather than relying on Json.Net to try to figure it out itself. Of course, that requires a bit more code, but it isn't really too hard to make a converter. I can provide an example if you need it.