Serializing UTC time c#
Asked Answered
A

1

9

I have a DateTime datamember in a datacontract. By default the datacontractserializer serializes the UTC time to yyyy-MM-ddTHH:mm:ss.fffffffZ format. I need it in yyyy-MM-ddTHH:mm:ss.000Z format but have no control over the datacontracts. So is there anything I can do with the DataContractSerializer that would give me the UTC time in the format I want. Thanks

Assessment answered 8/12, 2015 at 23:2 Comment(0)
W
4

I've created an implementation that uses an implementation of the IDataContractSurrogate to serialize your DTO's with DTO's you own.

DTO's

You didn't provide DTO's so I've created one to be your original DTO (that you can't change) and one replacement DTO that we own. They will have the same public signature, except their DateTime properties are changed to String types.

/// <summary>
/// original DTO, is fixed
/// </summary>
[DataContract]
class DTO
{
    [DataMember]
    public DateTime FirstDate { get; set; }

}

/// <summary>
/// Our own DTO, will act as surrogate
/// </summary>
[DataContract(Name="DTO")]
class DTO_UTC
{
    [DataMember]
    public string FirstDate { get; set; }
}

IDataContractSurrogate

The IDataContractSurrogate provides the methods needed to substitute one type for another during serialization and deserialization.

I used simple reflection here. If you need better performance look into emiting generated code between the types or even generate the target types.

public class DTOTypeSurrogate : IDataContractSurrogate
{
    // this determines how you want to replace one type with the other
    public Type GetDataContractType(Type type)
    {
        if (type == typeof(DTO))
        {
            return typeof(DTO_UTC);
        }
        return type;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        // do we know this type
        if (targetType == typeof(DTO))
        {
            // find each DateTime prop and copy over
            var objType = obj.GetType();
            var target = Activator.CreateInstance(targetType);
            foreach(var prop in targetType.GetProperties())
            {
                // value comes in
                var src =  objType.GetProperty(prop.Name);
                // do we need special handling
                if (prop.PropertyType == typeof(DateTime))
                {
                    DateTime utcConvert;
                    // parse to a datetime
                    if (DateTime.TryParse(
                        (string) src.GetValue(obj),
                        System.Globalization.CultureInfo.InvariantCulture, 
                        System.Globalization.DateTimeStyles.AdjustToUniversal, 
                        out utcConvert))
                    {
                        // store 
                        prop.SetValue(target, utcConvert);
                    }
                }
                else
                {
                    // store non DateTime types
                    prop.SetValue(target, src);
                }
            }
            return target;
        }
        return obj;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        // go from DTO to DTO_UTC
        if (targetType == typeof(DTO_UTC))
        {
            var utcObj = Activator.CreateInstance(targetType);
            var objType = obj.GetType();
            // find our DateTime props
            foreach(var prop in objType.GetProperties())
            {
                var src = prop.GetValue(obj);
                if (prop.PropertyType == typeof(DateTime))
                {
                    // create the string
                    var dateUtc = (DateTime)src;
                    var utcString = dateUtc.ToString(
                        "yyyy-MM-ddThh:mm:ss.000Z", 
                        System.Globalization.CultureInfo.InvariantCulture);
                    // store
                    targetType.GetProperty(prop.Name).SetValue(utcObj, utcString);
                } else
                {
                    // normal copy
                    targetType.GetProperty(prop.Name).SetValue(utcObj, src);
                }
            }
            return utcObj;
        }
        // unknown types return the original obj
        return obj;
    }
   // omitted the other methods in the interfaces for brevity
}

Usage with the serializer

Here we create the DataContractSerializer and provide it with an instantiated DTO and after serialization we reverse the process to check if the result is the same.

var surrogateSerializer =
    new DataContractSerializer(
        typeof(DTO),
        new Type[] {}, 
        Int16.MaxValue, 
        false, 
        true, 
        new DTOTypeSurrogate()); // here we provide our own implementation

var ms = new MemoryStream();
// test data
var testDto = new DTO { 
    FirstDate = new DateTime(2015, 12, 31, 4, 5, 6, DateTimeKind.Utc) };
// serialize
surrogateSerializer.WriteObject(ms, testDto);
// debug
var wireformat = Encoding.UTF8.GetString(ms.ToArray());

//reset
ms.Position = 0;
//deserialize
var dtoInstance = (DTO) surrogateSerializer.ReadObject(ms);
// verify we have the same data returned
Debug.Assert(dtoInstance.FirstDate == testDto.FirstDate);
Westhead answered 9/12, 2015 at 22:26 Comment(7)
Thanks for taking the time to answer my question. Your solution works. The DTO class in my case has about 50 properties. I just need to serialize two props differently. Is there anyway I can include just the two props in the DTO_UTC and have the rest serialized with their default? What about changes to the DTO? DTO implements IExtensibleDataObject.Assessment
Also, l need to have the attributes removed on the nodes (xml doc). I saw a post about that here.Assessment
You can an extra check in the if something like if (prop.PropertyType == typeof(DateTime) && (prop.Name == "foo" || prop.Name=="bar"))Westhead
The attributes are added by the DataContractSerializer also in its default operation. I don't think you can alter that, unless you want to postprocess the result.Westhead
If I understand your implementation of the GetObjectToSerialize method correctly, it seems like I need to make an exact copy of the DTO and use it for DTO_UTC. And the nodes with the attributes may be a deal breaker or may be not. I will have to see. I am also looking at post processing the whole thing. I just have to search the two nodes and parse the value.Assessment
Yes, that is correct, the DTO_UTC uses a string instead of a datetime for the values you want to rewrite.Westhead
Although I went with the different route to handle my situation, your response is the correct way to deal with this issue.Assessment

© 2022 - 2024 — McMap. All rights reserved.