2009-08-27 9 views
24

Ho un oggetto, ha una proprietà DateTime ... Voglio passare quell'oggetto da un gestore .ashx in una pagina web tramite AJAX/JSON ... Non voglio usare i controlli di terze parti ...Custom JavaScriptConverter per DateTime?

quando faccio questo:

new JavaScriptSerializer().Serialize(DateTime.Now); 

ottengo questo:

"\/Date(1251385232334)\/" 

ma voglio "2009/08/26" (Nevermind localizzazione ... la mia app è molto localizzato, in modo da le mie ipotesi di formattazione della data non sono in discussione in questa domanda). Se faccio/registrare un convertitore personalizzato

public class DateTimeConverter : JavaScriptConverter 
{ 
    public override IEnumerable<Type> SupportedTypes 
    { 
     get { return new List<Type>() { typeof(DateTime), typeof(DateTime?) }; } 
    } 

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) 
    { 
     Dictionary<string, object> result = new Dictionary<string, object>(); 
     if (obj == null) return result; 
     result["DateTime"] = ((DateTime)obj).ToShortDateString(); 
     return result; 
    } 

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) 
    { 
     if (dictionary.ContainsKey("DateTime")) 
      return new DateTime(long.Parse(dictionary["DateTime"].ToString()), DateTimeKind.Unspecified); 
     return null; 
    } 
} 

tanto sono il risultato (in quanto il valore di ritorno del metodo serialize personalizzato è un dizionario):

{"DateTime":"8/27/2009"} 

così ora nel mio JavaScript, invece di facendo

somePerson.Birthday 

devo fare

somePerson.Birthday.DateTime 

    or 

somePerson.Birthday["DateTime"] 

Come posso fare in modo che il convertitore personalizzato restituisca una stringa diretta in modo da poter avere Javascript pulito?

+3

quasi 5 anni dopo ... e non utilizzerei mai un formato data di "27/08/2009" in JSON ... Uso sempre ISO8601: 2009-08-27T00: 00: 00Z –

risposta

20

JavaScriptSerializer può sicuramente fare ciò che desideri.

È possibile personalizzare la serializzazione eseguita da JavaScriptSerializer per qualsiasi tipo creando un convertitore personalizzato e registrandolo con il serializzatore. Se si dispone di una classe chiamata una persona, potremmo creare un convertitore in questo modo:

public class Person 
{ 
    public string Name { get; set; } 
    public DateTime Birthday { get; set; } 
} 

public class PersonConverter : JavaScriptConverter 
{ 
    private const string _dateFormat = "MM/dd/yyyy"; 

    public override IEnumerable<Type> SupportedTypes 
    { 
     get 
     { 
      return new[] { typeof(Person) }; 
     } 
    } 

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) 
    { 
     Person p = new Person(); 
     foreach (string key in dictionary.Keys) 
     { 
      switch (key) 
      { 
       case "Name": 
        p.Name = (string)dictionary[key]; 
        break; 

       case "Birthday": 
        p.Birthday = DateTime.ParseExact(dictionary[key] as string, _dateFormat, DateTimeFormatInfo.InvariantInfo); 
        break; 
      } 
     } 
     return p; 
    } 

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) 
    { 
     Person p = (Person)obj; 
     IDictionary<string, object> serialized = new Dictionary<string, object>(); 
     serialized["Name"] = p.Name; 
     serialized["Birthday"] = p.Birthday.ToString(_dateFormat); 
     return serialized; 
    } 
} 

e usarlo in questo modo:

JavaScriptSerializer serializer = new JavaScriptSerializer(); 
serializer.RegisterConverters(new[] { new PersonConverter() }); 

Person p = new Person 
      { 
       Name = "User Name", 
       Birthday = DateTime.Now 
      }; 

string json = serializer.Serialize(p); 
Console.WriteLine(json); 
// {"Name":"User Name","Birthday":"12/20/2010"} 

Person fromJson = serializer.Deserialize<Person>(json); 
Console.WriteLine(String.Format("{0}, {1}", fromJson.Name, fromJson.Birthday)); 
// User Name, 12/20/2010 12:00:00 AM 
+0

guarda ... una VECCHIA domanda con una VECCHIA risposta. Ho aggiornato per accettare il tuo –

-1

link text Questo esempio funziona

JavaScriptSerializer serializer = new JavaScriptSerializer(); 

DateTime dt = DateTime.Now; 
DateTime dt1 = dt; 

string jsonDateNow = serializer.Serialize(dt1); 
+0

quell'esempio lo fa sicuramente non funziona ... è esattamente lo stesso di quello che ho elencato in cima al mio post ... quindi esce solo "\/Date (1251467694063) \ /" che non è quello che voglio. ma grazie 4 il link all'altro thread simile –

+0

Questo esempio funziona perfettamente per me, la cosa curiosa è che se non crei la variabile "dt1" serializzata errata. –

+0

quando si esegue quel codice, cosa si ottiene come output? –

2

la risposta è: non è possibile utilizzare JavaScriptConverter in questo modo ... non ha le capacità.

ma per riferimento:

How do I format a Microsoft JSON date? http://blog.stevenlevithan.com/archives/date-time-format

Se ti interessa, quello che ho finito per fare è stato l'aggiunta di un metodo per il prototipo di stringa javascript per rendere questo più facile per me in codice:

String.prototype.dateFromJSON = function() { 
    return eval(this.replace(/\/Date\((\d+)\)\//gi, "new Date($1)")); 
}; 

questo è ancora doloroso da utilizzare nella carne del codice perché devi chiamare costantemente dateFromJSON() dappertutto ... che è stupido.

4

In realtà c'è un modo brutto, creare un JavaScriptConverter per il contenitore (Persona/articolo/altro) Classe

Contenitore:

public class Article 
{ 
    public int Id { get; set; } 
    public string Title { get; set; } 
    public DateTime Date { get; set; } 
} 

Converter:

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

    public override object Deserialize(
     IDictionary<string, object> dictionary, 
     Type type, JavaScriptSerializer serializer) 
    { 
     DateTime date = 
      DateTime.ParseExact(dictionary["date"] as string, "s", null); 

     return 
      new Article() 
      { 
       Id = (int)dictionary["id"], 
       Title = dictionary["title"] as string, 
       Date = date 
      }; 
    } 

    public override IDictionary<string, object> Serialize(
     object obj, JavaScriptSerializer serializer) 
    { 
     var article = obj as Article; 
     var result = new Dictionary<string,object>(); 

     if (article != null) 
     { 
      this.SerializeInternal(article, result); 
     } 

     return result; 
    } 

    private void SerializeInternal(
     Article article, IDictionary<string, object> result) 
    { 
     result.Add("id", article.Id); 
     result.Add("title", article.Title); 
     result.Add("date", article.Date.ToString("s")); 
    } 
} 

vissero felici e contenti ...

var serializer = new JavaScriptSerializer(); 

serializer.RegisterConverters(
    new JavaScriptConverter[] { 
     new ArticleJavaScriptConverter() 
    }); 

var expected = new Article() 
{ 
    Id = 3, 
    Title = "test", 
    Date = DateTime.Now 
}; 


// {"id":3,"title":"test","date":"2009-12-02T05:12:00"} 
var json = serializer.Serialize(article); 

var actual = serializer.Deserialize<Article>(json); 

Assert.AreEqual(expected, actual); 
0

So che questo sembra davvero stupido, ma finora non ho trovato niente di meglio ... Sto ancora cercando, quindi i commenti sono ben accetti.

new JavaScriptSerializer().Serialize(DateTime.Now).Replace("\"\\/", "").Replace("\\/\"", ""); 

Questo solo rimuove le virgolette e barre, quindi l'uscita è solo Date(123456789) che, anche se tecnicamente non è un letterale, è compreso dal browser come un valore data effettiva e non una stringa.

in JSON, sarebbe simile a questa

{"myDate":Date(123456789)} 

Un hack, suppongo. Se questo è effettivamente implementato nel codice di produzione, lo ricollegherei personalmente, in un metodo di estensione come FormatForDates() o avvolgo il serializzatore stesso come in un pattern di decoratore ... o in questo caso, un "undecorator". Devo davvero perdere la barca sul perché questo sembra così difficile. Voglio solo rendere un appuntamento, gente! :-p

+0

Ho usato un'espressione regolare per ottenere un risultato simile: 'nuovo Regex (@" {"" DateTime "" :("" [0-9/-] + "")} "). Sostituisci (s," $ 1 ") '. L'esult sembra '{" myDate ":" 5/3/2011 "}'. – Ryan

10

Ecco un miglioramento per la risposta accettata.

Utilizzo di generici, passaggio di un tipo e utilizzo di reflection per determinare le proprietà datetime.

public class ExtendedJavaScriptConverter<T> : JavaScriptConverter where T : new() 
{ 
    private const string _dateFormat = "dd/MM/yyyy"; 

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

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) 
    { 
     T p = new T(); 

     var props = typeof(T).GetProperties(); 

     foreach (string key in dictionary.Keys) 
     { 
      var prop = props.Where(t => t.Name == key).FirstOrDefault(); 
      if (prop != null) 
      { 
       if (prop.PropertyType == typeof(DateTime)) 
       { 
        prop.SetValue(p, DateTime.ParseExact(dictionary[key] as string, _dateFormat, DateTimeFormatInfo.InvariantInfo), null); 

       } 
       else 
       { 
        prop.SetValue(p, dictionary[key], null); 
       } 
      } 
     }     

     return p; 
    }  

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) 
    { 
     T p = (T)obj; 
     IDictionary<string, object> serialized = new Dictionary<string, object>(); 

     foreach (PropertyInfo pi in typeof(T).GetProperties()) 
     { 
      if (pi.PropertyType == typeof(DateTime)) 
      { 
       serialized[pi.Name] = ((DateTime)pi.GetValue(p, null)).ToString(_dateFormat); 
      } 
      else 
      { 
       serialized[pi.Name] = pi.GetValue(p, null); 
      } 

     } 

     return serialized; 
    } 

    public static JavaScriptSerializer GetSerializer() 
    { 
     JavaScriptSerializer serializer = new JavaScriptSerializer(); 
     serializer.RegisterConverters(new[] { new ExtendedJavaScriptConverter<T>() }); 

     return serializer; 
    } 
} 

L'uso è semplice:

JavaScriptSerializer serialiser = ExtendedJavaScriptConverter<Task>.GetSerializer(); 

Speranza che aiuta qualcuno.

+0

Bella implementazione! – Oybek

+0

Bello. Grazie per questo. –

+0

Questo non funzionerebbe dove il mio tipo aveva una lista, cioè una tabella anonima: Elenco > Continuavo a ricevere l'errore: Eccezione di conteggio dei parametri non corrispondente quando si chiama PropertyInfo.GetValue su "prop.SetValue (p, dizionario [chiave], null); " – Tod

7

Esiste in realtà un modo semplice per farlo senza conoscere il tipo di wrapper o anche la necessità di un oggetto wrapper.

Si utilizza JavaScriptConverter per convertire l'oggetto in un Uri che implementa anche IDictionary. JavaScriptSerializer serializzerà questo come una stringa.

Questo mod è descritto qui:

http://blog.calyptus.eu/seb/2011/12/custom-datetime-json-serialization/

+0

Pazzo, questo ha funzionato! Grazie! – Tengiz

+0

Questo è ciò che mi serviva per convertire un oggetto DateTime come una proprietà di un oggetto anonimo senza trasformare l'oggetto DateTime in un oggetto che ha un datetime. TL; DR; Fa quello che dovrebbe. – Tod

0

Una conversione vb.net della risposta di @sambomartin. Tutto il merito di questo va a lui. Ho appena incollato questo qui nel caso qualcuno abbia bisogno di questo per vb.net.

Inoltre, l'ho reso ricorsivo e ho aggiunto la possibilità di sovrascrivere i nomi di proprietà predefiniti con annotazioni di dati XmlElement. (XmlElementAttribute)

Imports System.Web.Script.Serialization 
Imports System.Linq 
Imports System.Globalization 
Imports System.Xml.Serialization 

Public Class ExtendedJavaScriptSerializer(Of T As New) 
    Inherits JavaScriptConverter 

    Private Const _dateFormat As String = "dd/MM/yyyy" 



    Public Overrides Function Deserialize(dictionary As IDictionary(Of String, Object), type As Type, serializer As JavaScriptSerializer) As Object 
     Dim p As New T() 
     Dim props = GetType(T).GetProperties() 

     For Each key As String In dictionary.Keys 
      Dim prop = props.Where(Function(x) x.Name = key).FirstOrDefault() 
      If prop IsNot Nothing Then 
       If prop.PropertyType = GetType(DateTime) Then 
        prop.SetValue(p, DateTime.ParseExact(CStr(dictionary(key)), _dateFormat, DateTimeFormatInfo.InvariantInfo), Nothing) 
       Else 
        prop.SetValue(p, dictionary(key), Nothing) 
       End If 
      End If 
     Next 

     Return p 

    End Function 

    Public Overrides Function Serialize(obj As Object, serializer As JavaScriptSerializer) As IDictionary(Of String, Object) 
     Dim serialized As IDictionary(Of String, Object) = New Dictionary(Of String, Object) 
     CheckProperties(obj, serialized) 
     Return serialized 
    End Function 

    Public Overrides ReadOnly Property SupportedTypes As IEnumerable(Of Type) 
     Get 
      Return {GetType(T)} 
     End Get 
    End Property 

    Private Sub CheckProperties(obj As Object, ByRef serialized As IDictionary(Of String, Object)) 
     If obj Is Nothing Then Return 

     Dim objType As Type = obj.GetType() 

     For Each pi In objType.GetProperties() 
      ' define serialization attribute name from ' 
      ' xmlelement dataannotation' 
      Dim displayname As String = pi.Name 
      Dim attrs() As Object = pi.GetCustomAttributes(True) 
      For Each attr In attrs 
       If GetType(XmlElementAttribute) = attr.GetType() Then 
        displayname = CType(attr, XmlElementAttribute).ElementName 
       End If 
      Next 
      ' fix date format' 
      If pi.PropertyType = GetType(DateTime) Then 
       serialized(displayname) = CType(pi.GetValue(obj, Nothing), DateTime).ToString(_dateFormat) 
      Else 
       ' recursive' 
       If pi.PropertyType.Assembly = objType.Assembly Then 
        CheckProperties(pi.GetValue(obj, Nothing), serialized) 
       Else 
        If pi.GetValue(obj, Nothing) IsNot Nothing Then 
         serialized(displayname) = pi.GetValue(obj, Nothing) 
        End If 
       End If 
      End If 
     Next 
    End Sub 

    Public Shared Function GetSerializer() As JavaScriptSerializer 
     Dim serializer As New JavaScriptSerializer 
     serializer.RegisterConverters({New ExtendedJavaScriptSerializer(Of T)}) 
     Return serializer 
    End Function 

End Class 
0

Ho avuto un problema simile in cui ho voluto classe SensorReading avente proprietà enum 'tipo' e 'unità' per serializzare con il nome di valori Enum.(Risultato di default è 0 se l'Enum non ha valore numerico esplicito)

Prima il risultato serializzato si presentava così:

[{"id":"0","type":0,"value":"44.00","unit":0}] 

Quello che volevo era questo:

[{"id":"0","type":"temperature","value":"44.00","unit":"C"}] 

Nell'esempio funzione di seguito registro un serializzatore personalizzato 'EnumConverter < SensorReading>' prima di serializzare un oggetto.

public static string ToSJSon(object obj) 
{ 
    var jss = new JavaScriptSerializer(); 
    jss.RegisterConverters(new[] { new EnumConverter<SensorReading>() }); 


    return jss.Serialize(obj); 
} 

Ecco l'EnumConverter generica

public class EnumConverter<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(String.Format("'{0}' does not yet implement 'Deserialize", this.GetType().Name)); 
    } 

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) 
    { 

     IDictionary<string, object> serialized = new Dictionary<string, object>(); 
     if (obj.GetType() == typeof(T)) 
     { 
      if (obj.GetType().IsEnum) 
      { 
       serialized[obj.GetType().Name] = Enum.GetName(obj.GetType(), obj); ; 
      } 
      else 
      { 
       var sourceType = obj.GetType(); 
       var properties = sourceType.GetProperties(); 
       foreach (PropertyInfo property in properties) 
       { 
        if (property.CanRead) 
        { 
         if (property.PropertyType.IsEnum) 
         { 
          var str = Enum.GetName(property.PropertyType, property.GetValue(obj, null)); 
          serialized[property.Name] = str; 
         } 
         else 
         { 
          serialized[property.Name] = property.GetValue(obj, null); 
         } 

        } 
       } 
      } 
     } 

     return serialized; 

    } 

} 

Il serializzatore personalizzato annuncia che serializza oggetti di tipo T e in serializzare loop proprietà tutto leggibili. Se la proprietà è un'enumerazione, restituisce il nome anziché il valore nel dizionario.

Questo potrebbe essere esteso ad altri tipi di proprietà che non serializzano come vogliamo.

Ho aggiunto un test separato se il serializzatore (i) personalizzato T risulta essere Enum. Quindi, invece, emettere il nome della classe Enum e il suo valore; Il risultato sarà il seguente aspetto:

[{"id":"0","type":{"ReadingType":"temperature"},"value":"44.00","unit":{"ReadingUnit":"C"}}] 

che può essere una migliore uscita se si prevede di deserializzare e vogliono sapere che tipo Enum il valore appartiene.