57 votes

Pourquoi DateTime.MinValue ne peut pas être sérialisé dans les fuseaux horaires en avance sur l'UTC ?

Je rencontre des problèmes avec un service WCF REST. Certaines propriétés de l'objet filaire que j'essaie de renvoyer ne sont pas définies, ce qui se traduit par DateTime.MinValue pour les propriétés de type DateTime. Le service renvoie un document vide (avec le statut HTTP 200 ? ??). Lorsque j'essaie d'appeler moi-même la sérialisation JSON, l'exception qui est levée est la suivante :

SerializationException : Les valeurs DateTime qui sont supérieures à DateTime.MaxValue ou inférieures à DateTime.MinValue lorsqu'elles sont converties en UTC ne peuvent pas être sérialisées en JSON.

Ce problème peut être reproduit en exécutant le code suivant dans une application console :

DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(DateTime));
MemoryStream m = new MemoryStream();
DateTime dt = DateTime.MinValue;

// throws SerializationException in my timezone
ser.WriteObject(m, dt);
string json = Encoding.ASCII.GetString(m.GetBuffer());
Console.WriteLine(json);

Pourquoi ce comportement ? Je pense que c'est lié à mon fuseau horaire (GMT+1). Comme DateTime.MinValue est default(DateTime), je m'attendrais à ce que cela puisse être sérialisé sans problème.

Des conseils pour que mon service REST se comporte bien ? Je ne veux pas modifier mon DataContract.

0 votes

Pouvez-vous rendre votre DateTime nullable et utiliser null comme valeur par défaut ?

0 votes

@Gabe : Je suppose que je pourrais. Cela semble stupide de changer mon type pour contourner un détail de sérialisation. Mais c'est probablement la voie la plus pragmatique.

76voto

Nick Martyshchenko Points 2987

Le principal problème est DateTime.MinValue a DateTimeKind.Unspecified genre. Il est défini comme suit :

MinValue = new DateTime(0L, DateTimeKind.Unspecified);

Mais ce n'est pas un vrai problème, cette définition entraîne un problème lors de la sérialisation. La sérialisation de JSON DateTime est effectuée par :

System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTime(DateTime value)

Malheureusement, il est défini comme suit :

...

if (value.Kind != DateTimeKind.Utc)
{
    long num = value.Ticks - TimeZone.CurrentTimeZone.GetUtcOffset(value).Ticks;
    if ((num > DateTime.MaxValue.Ticks) || (num < DateTime.MinValue.Ticks))
    {
        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString("JsonDateTimeOutOfRange"), new ArgumentOutOfRangeException("value")));
    }
}

...

Donc il ne prend pas en compte Unspecified et le traite comme Local . Pour éviter cette situation, vous pouvez définir votre propre constante :

MinValueUtc = new DateTime(0L, DateTimeKind.Utc);

ou

MinValueUtc = DateTime.MinValue.ToUniversalTime();

Ça a l'air bizarre bien sûr, mais ça aide.

0 votes

C'est une bonne explication. Cela explique également pourquoi la sérialisation XML fonctionne et pas JSON. Mon problème est que la valeur minimale est présente parce qu'elle n'est pas définie. Au lieu d'ajouter un simple attribut, je vais devoir définir cette MinValueUtc personnalisée pour toutes les propriétés de date.

0 votes

Vous pouvez envisager de travailler (ou simplement de stocker) avec des dates toujours en UTC en les convertissant en LocalTime juste avant de devoir les afficher. Ainsi, après l'init, soit dans le constructeur, soit dans le setter approprié, convertissez la valeur de date 'entrante' en UTC via .ToUniversalTime(). Cela permet de résoudre le problème de la valeur par défaut.

0 votes

Cela n'a pas fonctionné pour moi, en fixant la valeur à new DateTime(0L, DateTimeKind.Utc) ; cela fait également planter la sérialisation.

18voto

user1505015 Points 41

Essayez d'ajouter ceci sur n'importe quel membre de DateTime

[DataMember(IsRequired = false, EmitDefaultValue = false)]

La plupart de ces erreurs se produisent parce que la valeur par défaut de l'option datetime es DateTime.MinValue qui est de l'année de 1 et la sérialisation JSON est de l'année 1970.

6voto

Adam Robinson Points 88472

Si votre fuseau horaire est GMT+1, la valeur UTC de l'option DateTime.MinValue dans votre fuseau horaire sera d'une heure de moins que DateTime.MinValue .

13 votes

Oui, c'est ce que je pensais. Mais que faire ? N'est-il pas étrange que la valeur par défaut d'une classe très courante du framework ne puisse être sérialisée dans une moitié du monde ?

5voto

Daniel Cai Points 51

En utilisant ce constructeur :

public DataContractJsonSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, IDataContractSurrogate dataContractSurrogate, bool alwaysEmitTypeInformation)

exemple de code :

DataContractJsonSerializer serializer = new DataContractJsonSerializer(o.GetType(), null, int.MaxValue, false, new DateTimeSurrogate(), false);

 public class DateTimeSurrogate : IDataContractSurrogate
    {

        #region IDataContractSurrogate 

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

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

        public Type GetDataContractType(Type type)
        {
            return type;
        }

        public object GetDeserializedObject(object obj, Type targetType)
        {
                   return obj;
        }

        public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
        {

        }

        public object GetObjectToSerialize(object obj, Type targetType)
        {
            if (obj.GetType() == typeof(DateTime))
            {
                DateTime dt = (DateTime)obj;
                if (dt == DateTime.MinValue)
                {
                    dt = DateTime.MinValue.ToUniversalTime();
                    return dt;
                }
                return dt;
            }
            if (obj == null)
            {
                return null;
            }
            var q = from p in obj.GetType().GetProperties()
                    where (p.PropertyType == typeof(DateTime)) && (DateTime)p.GetValue(obj, null) == DateTime.MinValue
                    select p;
            q.ToList().ForEach(p =>
            {
                p.SetValue(obj, DateTime.MinValue.ToUniversalTime(), null);
            });
            return obj;
        }

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

        public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
        {
            return typeDeclaration;
        }

        #endregion
    }

1 votes

Alors, où pourrais-je utiliser ce constructeur ? Actuellement, j'ajoute simplement WebMessageFormat.Json à l'attribut WebInvoke. Avez-vous une idée de la façon de combiner cette technique dans le style déclaratif ?

3voto

Reza Points 115

Je pense qu'un moyen plus élégant consiste à demander au sérialiseur de ne pas émettre la valeur par défaut pour les champs DateTime. Cela permettra d'économiser un octet lors du transfert et un traitement lors de la sérialisation pour les champs pour lesquels vous n'avez pas de valeur. Exemple :

[DataContract]
public class Document {
    [DataMember] 
    public string Title { get; set; }
    [DataMember(IsRequired = false, EmitDefaultValue = false)] 
    public DateTime Modified { get; set; } 
}

ou vous pouvez utiliser les Nullables. Exemple :

[DataContract]
public class Document {
    [DataMember] 
    public string Title { get; set; }
    [DataMember] 
    public DateTime? Modified { get; set; } 
}

Tout dépend des exigences et des restrictions que vous pourriez avoir dans votre projet. Parfois, vous ne pouvez pas simplement changer les types de données. Dans ce cas, vous pouvez toujours tirer parti de DataMember et garder les types de données intacts.

Dans l'exemple ci-dessus, si vous avez new Document() { Title = "Test Document" } du côté serveur, lorsqu'il est sérialisé en JSON, il vous donnera {"Title": "Test Document"} afin qu'il soit plus facile de le traiter en JavaScript ou avec tout autre client de l'autre côté du fil. En JavaScript, si vous utilisez JSON.Parse() et essayez de lire le fichier, vous obtiendrez en retour undefined . Dans les langages typés, la valeur par défaut de cette propriété dépend du type (ce qui est généralement le comportement attendu).

library.GetDocument(id).success(function(raw){ 
    var document = JSON.Parse(raw);
    var date = document.date; // date will be *undefined*
    ...
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X