3

I have an object that has some timespan prop, i'm using .net serializer in my MVC Action.(my action returns JsonResult type) timeSpan Prop deserialize like this:

"EndTime":{"Hours":9,"Minutes":0,"Seconds":0,"Milliseconds":0,"Ticks":324000000000,"Days":0,"TotalDays":0.375,"TotalHours":9,"TotalMilliseconds":32400000,"TotalMinutes":540,"TotalSeconds":32400}

after that i call this action in a windows application, and i need to deserialize this object using newtonesoft. but i recive this error :

cannot deserialize the current JSON object(e.g. {"name":"value"}) into type 'system.Nullable[System.TimeSpan]' because the type requires a JSON primitivevalue (e.g. string, number, boolean, null) to deserialize correctly.To fix this error either change JSON to a JSON primitive value (e.g. string,number, boolean, null) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection typelike an array or List) that can be deserialized from JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.Path 'InTime.Hours' line 1

It's important to know, that my server project must use only microsoft .net serializer, and win app must use only Newtonesoft deserializer !

after all i guess, if serializer, make name:value the problem will be solved, maybe somethings like this

{"EndTime":"090000"}

but i don't know how can force serializer to change TimeSpan format !

maybe somethings like this ???? :-) :

public class Attendance
{
    public int ID { get; set; }
    public int PID { get; set; }

   [Define Format in serialization here (maybe !)]
    public System.TimeSpan time { get; set; }

}
dr_traz
  • 31
  • 1
  • 3
  • I think the json format may be wrong..instead of.. "EndTime":{"Hours":9,"Minutes":0,"Seconds":0,"Milliseconds":0,"Ticks":324000000000,"Days":0,"TotalDays":0.375,"TotalHours":9,"TotalMilliseconds":32400000,"TotalMinutes":540,"TotalSeconds":32400} shouldnt it be... {"Hours":9,"Minutes":0,"Seconds":0,"Milliseconds":0,"Ticks":324000000000,"Days":0,"TotalDays":0.375,"TotalHours":9,"TotalMilliseconds":32400000,"TotalMinutes":540,"TotalSeconds":32400} – code Jun 27 '15 at 16:35
  • i don't made it, it made by ..net framework stardard Json serializer ! – dr_traz Jun 27 '15 at 16:47

1 Answers1

2

Your server is outputting the TimeSpan using JavaScriptSerializer format whereas your client is expecting the time span in Json.NET format.

You can see the two formats by testing, as follows:

Debug.WriteLine(new JavaScriptSerializer().Serialize(DateTime.Today - DateTime.Today.AddDays(-1)))

which prints:

{"Ticks":864000000000,"Days":1,"Hours":0,"Milliseconds":0,"Minutes":0,"Seconds":0,"TotalDays":1,"TotalHours":24,"TotalMilliseconds":86400000,"TotalMinutes":1440,"TotalSeconds":86400}

Versus

Debug.WriteLine(JsonConvert.SerializeObject(DateTime.Today - DateTime.Today.AddDays(-1)))

which prints a much more concise:

"1.00:00:00"

It would be quite easy to make Json.NET parse TimeSpan objects in JavaScriptSerializer format, but your request is the reverse: make JavaScriptSerializer output in Json.NET format. That's more difficult because Json.NET represents a time span as a simple string, but a JavaScriptConverter only allows for mapping a type to a custom JSON object -- not a string.

Thus it's necessary to apply a converter to all types containing a TimeSpan. Here's a generic converter that does a default conversion then overrides all TimeSpan-valued properties and fields. It will be performant for smaller classes (such as your example class) but will have poor performance for serializing large root classes, for which you might need to write a custom converter:

public class TypeWithTimespanConverter<T> : JavaScriptConverter
{
    public override IEnumerable<Type> SupportedTypes
    {
        get
        {
            return new[] { typeof(T) };
        }
    }

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        // Use a "fresh" JavaScriptSerializer here to avoid infinite recursion.
        var hasTimeSpan = (T)obj;

        // Generate a default serialization.  Is there an easier way to do this?
        var defaultSerializer = new JavaScriptSerializer();
        var dict = defaultSerializer.Deserialize<Dictionary<string, object>>(defaultSerializer.Serialize(obj));

        foreach (var pair in dict.ToList())
        {
            if (!(pair.Value is IDictionary))
                continue;
            TimeSpan span;
            if (obj.TryGetPropertyValueOfType<TimeSpan>(pair.Key, out span))
            {
                dict[pair.Key] = span.ToString();
                continue;
            }
            TimeSpan? spanPtr;
            if (obj.TryGetPropertyValueOfType<TimeSpan?>(pair.Key, out spanPtr))
            {
                if (spanPtr == null)
                    dict[pair.Key] = null;
                else
                    dict[pair.Key] = spanPtr.Value.ToString();
                continue;
            }
        }

        return dict;
    }
}

public static class ObjectExtensions
{
    public static bool TryGetPropertyValueOfType<T>(this object obj, string name, out T value)
    {
        if (obj == null)
            throw new NullReferenceException();
        var type = obj.GetType();
        var property = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
        if (property != null
            && property.GetIndexParameters().Length == 0
            && typeof(T).IsAssignableFrom(property.PropertyType)
            && property.GetGetMethod(false) != null)
        {
            value = (T)property.GetGetMethod(false).Invoke(obj, new object[0]);
            return true;
        }

        var field = type.GetField(name, BindingFlags.Public | BindingFlags.Instance);
        if (field != null
            && typeof(T).IsAssignableFrom(field.FieldType))
        {
            value = (T)field.GetValue(obj);
            return true;
        }

        value = default(T);
        return false;
    }
}

And then use it like:

        var attendance = new Attendance { ID = 101, PID = -101, time = DateTime.Today - DateTime.Today.AddDays(-1).AddHours(-1) };

        var serializer = new JavaScriptSerializer();
        serializer.RegisterConverters(new JavaScriptConverter[] { new TypeWithTimespanConverter<Attendance>() });

        var json = serializer.Serialize(attendance);                        // Serialize with JavaScriptSerializer and TypeWithTimespanConverter
        Debug.WriteLine(json);                                              // Prints {"ID":101,"PID":-101,"time":"1.01:00:00"}
        var attendance2 = JsonConvert.DeserializeObject<Attendance>(json);  // Deserialize with Json.NET
        Debug.Assert(attendance2.time == attendance.time);                  // Assert that the timespan was converted

To configure the converter in ASP.NET services, see How to: Configure ASP.NET Services in ASP.NET AJAX.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • hi again, i have not any problem for deserialize using JavaScriptSerializer, – dr_traz Jun 28 '15 at 05:01
  • @dr_traz - to clarify, you want to switch your client from Json.NET to `JavaScriptSerializer`? That's sort of a second question, but see here: see here: http://stackoverflow.com/questions/4383425/javascriptserializer-not-deserializing-datetime-timespan-properly – dbc Jun 28 '15 at 05:04