DataContractJsonSerializer - Deserializing DateTime within List<object>
Asked Answered
M

5

14

I'm having trouble using the System.Runtime.Serialization.Json.DataContractJsonSerializer class to deserialize DateTime instances contained within a List<object>. I cannot seem to get DateTime to deserialize back into the original type. The DataContractJsonSerializer always deserializes it into a string type with the format "/Date(1329159196126-0500)/". It'll serialize and deserialize fine if I run it through using a strongly typed List<DateTime>, however I am looking for way to get the serializer to identify and properly deserialize DateTimes when encountered within a simple list or array of object.

Note that DateTimes are the only type besides primitives and strings that this list will ever contain. Here is the code snippet I'm using to test this.

var list = new List<object> { 27, "foo bar", 12.34m, true, DateTime.Now };
var serializer = new DataContractJsonSerializer(typeof (List<object>));
using (MemoryStream ms = new MemoryStream())
{
    serializer.WriteObject(ms, list);
    ms.Position = 0;
    var deserializedList = serializer.ReadObject(ms) as List<object>;
}
Microanalysis answered 13/2, 2012 at 19:10 Comment(1)
You should think about changing the accepted answer to the one with 40 upvotes.Hyrax
W
10

This seems like very strange behavior, my guess is that it stems from DateTime not being a type that is recongnized in JSON. However, you can roll your own IDataContractSurrogate to modify the serialization/deserialization process.

To use this modify your sample code when you create the the serializer to this:

var serializer = new DataContractJsonSerializer(typeof(List<object>), null, int.MaxValue, false, new DateTimeDataContractSurrogate(), true);

Then add this class:

public class DateTimeDataContractSurrogate : IDataContractSurrogate
    {
        private static readonly Regex dateRegex = new Regex(@"/Date\((\d+)([-+])(\d+)\)/");
        private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        public object GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            // not used
            return null;
        }

        public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
        {
            // not used
            return null;
        }

        public Type GetDataContractType(Type type)
        {
            // not used
            return type;
        }

        public object GetDeserializedObject(object obj, Type targetType)
        {
            // for debugging
            //Console.WriteLine("GetDeserializedObject: obj = {0} ({1}), targetType = {2}", obj, obj.GetType(), targetType);

            // only act on List<object> types
            if (obj.GetType() == typeof(List<object>))
            {
                var objList = (List<object>)obj;

                List<object> copyList = new List<object>(); // a list to copy values into. this will be the list returned.
                foreach (var item in objList)
                {
                    string s = item as string;
                    if (s != null)
                    {
                        // check if we match the DateTime format
                        Match match = dateRegex.Match(s);
                        if (match.Success)
                        {
                            // try to parse the string into a long. then create a datetime and convert to local time.
                            long msFromEpoch;
                            if (long.TryParse(match.Groups[1].Value, out msFromEpoch))
                            {
                                TimeSpan fromEpoch = TimeSpan.FromMilliseconds(msFromEpoch);
                                copyList.Add(TimeZoneInfo.ConvertTimeFromUtc(epoch.Add(fromEpoch), TimeZoneInfo.Local));
                                continue;
                            }
                        }
                    }

                    copyList.Add(item); // add unmodified
                }

                return copyList;
            }

            return obj;
        }

        public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
        {
            // not used   
        }

        public object GetObjectToSerialize(object obj, Type targetType)
        {
            // for debugging
            //Console.WriteLine("GetObjectToSerialize: obj = {0} ({1}), targetType = {2}", obj, obj.GetType(), targetType);
            return obj;
        }

        public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            // not used
            return null;
        }

        public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
        {
            // not used
            return typeDeclaration;
        }
    }
Willable answered 19/2, 2012 at 8:8 Comment(3)
This is essentially the solution I ended up implementing. Thanks.Microanalysis
In order to support dates before 1970 your regex should be /Date\((-?\d+)([-+])(\d+)\)/)Swivet
This Regex fails when the Date is without time zone. I ended up using new Regex(@"/Date\((\d+)([-+])?(\d+)?\)/")Avaria
P
46

In the .NET Framework version 4.5 the DataContractJsonSerializer has a constructor that accepts a DataContractJsonSerializerSettings object that can be used to set the DateTimeFormat:

var ser = new DataContractJsonSerializer(typeof(CreateOmsEntryCommand),
              new DataContractJsonSerializerSettings
              {
                  DateTimeFormat = new DateTimeFormat("yyyy-MM-dd'T'HH:mm:ssZ")
              });
Podesta answered 20/3, 2014 at 22:43 Comment(2)
In terms of serialisation, this doesn't seem to work if the DateTime is not in UTC in the first place: it will have the values of the local time, but with the Z representing UTC (so be misinterpreted on the reading side). It's probably better to use yyyy-MM-dd'T'HH:mm:ssK instead.Speakeasy
I agree, use yyyy-MM-dd'T'HH:mm:ssK or yyyy-MM-dd'T'HH:mm:sszzz. Don't use yyyy-MM-dd'T'HH:mm:ssZ.Frodine
W
10

This seems like very strange behavior, my guess is that it stems from DateTime not being a type that is recongnized in JSON. However, you can roll your own IDataContractSurrogate to modify the serialization/deserialization process.

To use this modify your sample code when you create the the serializer to this:

var serializer = new DataContractJsonSerializer(typeof(List<object>), null, int.MaxValue, false, new DateTimeDataContractSurrogate(), true);

Then add this class:

public class DateTimeDataContractSurrogate : IDataContractSurrogate
    {
        private static readonly Regex dateRegex = new Regex(@"/Date\((\d+)([-+])(\d+)\)/");
        private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        public object GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            // not used
            return null;
        }

        public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
        {
            // not used
            return null;
        }

        public Type GetDataContractType(Type type)
        {
            // not used
            return type;
        }

        public object GetDeserializedObject(object obj, Type targetType)
        {
            // for debugging
            //Console.WriteLine("GetDeserializedObject: obj = {0} ({1}), targetType = {2}", obj, obj.GetType(), targetType);

            // only act on List<object> types
            if (obj.GetType() == typeof(List<object>))
            {
                var objList = (List<object>)obj;

                List<object> copyList = new List<object>(); // a list to copy values into. this will be the list returned.
                foreach (var item in objList)
                {
                    string s = item as string;
                    if (s != null)
                    {
                        // check if we match the DateTime format
                        Match match = dateRegex.Match(s);
                        if (match.Success)
                        {
                            // try to parse the string into a long. then create a datetime and convert to local time.
                            long msFromEpoch;
                            if (long.TryParse(match.Groups[1].Value, out msFromEpoch))
                            {
                                TimeSpan fromEpoch = TimeSpan.FromMilliseconds(msFromEpoch);
                                copyList.Add(TimeZoneInfo.ConvertTimeFromUtc(epoch.Add(fromEpoch), TimeZoneInfo.Local));
                                continue;
                            }
                        }
                    }

                    copyList.Add(item); // add unmodified
                }

                return copyList;
            }

            return obj;
        }

        public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
        {
            // not used   
        }

        public object GetObjectToSerialize(object obj, Type targetType)
        {
            // for debugging
            //Console.WriteLine("GetObjectToSerialize: obj = {0} ({1}), targetType = {2}", obj, obj.GetType(), targetType);
            return obj;
        }

        public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            // not used
            return null;
        }

        public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
        {
            // not used
            return typeDeclaration;
        }
    }
Willable answered 19/2, 2012 at 8:8 Comment(3)
This is essentially the solution I ended up implementing. Thanks.Microanalysis
In order to support dates before 1970 your regex should be /Date\((-?\d+)([-+])(\d+)\)/)Swivet
This Regex fails when the Date is without time zone. I ended up using new Regex(@"/Date\((\d+)([-+])?(\d+)?\)/")Avaria
S
5

If DataContractJsonSerializer isn't a must, here is a solution using Json.Net.

var list = new List<object> { 27, "foo bar", 12.34m, true, DateTime.Now };

string json = JsonConvert.SerializeObject(list);
var orgObj=JsonConvert.DeserializeObject<List<object>>(json);

This is the Json string

[27,"foo bar",12.34,true,"\/Date(1329161615596+0200)\/"]

and returned types are long,string,double,bool and DateTime

Semivitreous answered 13/2, 2012 at 19:31 Comment(0)
W
4

You could convert DateTime.Now to a string before serialization and
convert it back to DateTime after deserialization.

Conversion to string by:

string dateAsString = Convert.ToString(DateTime.Now);

Conversion back to DateTime after deserialization:

DateTime dateTime = Convert.ToDateTime(deserializedList[4]);

So the whole code would be like:

  string dateAsString = Convert.ToString(DateTime.Now);
  var list = new object[] { 27, "foo bar", 12.34m, true, dateAsString };

  var serializer = new DataContractJsonSerializer(typeof (List<object>));

  using (MemoryStream ms = new MemoryStream())
  {
    serializer.WriteObject(ms, list);
    ms.Position = 0;
    var deserializedList = serializer.ReadObject(ms) as List<object>;
    DateTime dateTime = Convert.ToDateTime(deserializedList[4]);
  }
Whitewall answered 16/2, 2012 at 13:38 Comment(0)
M
0

The solution to the issue is to use DataContractJsonSerializerSettings to change the DateTimeFormat to round trip o so that everything is preserved and then it will successfully convert.

Tested with Local and Utc

        public DataContractJsonSerializerSettings DataContractJsonSerializerSettings = new()
        {
            DateTimeFormat = new DateTimeFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffK"),
            SerializeReadOnlyTypes = true,
            EmitTypeInformation = EmitTypeInformation.Never,
            UseSimpleDictionaryFormat = true,
            IgnoreExtensionDataObject = true,
        };
Marylnmarylou answered 5/5, 2023 at 21:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.