Пользовательский JavaScriptConverter для DateTime? - PullRequest
26 голосов
/ 27 августа 2009

У меня есть объект, у него есть свойство DateTime ... Я хочу передать этот объект из обработчика .ashx обратно на веб-страницу через AJAX / JSON ... Я не хочу использовать сторонние элементы управления .. .

когда я делаю это:

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

Я понял:

  "\/Date(1251385232334)\/"

но я хочу "26.08.2009" (не обращайте внимания на локализацию ... мое приложение очень локализовано, поэтому мои предположения о форматировании даты не обсуждаются в этом вопросе). Если я сделаю / зарегистрирую пользовательский конвертер

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;
    }
}

тогда я получаю этот результат (так как возвращаемое значение пользовательского метода сериализации - словарь):

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

так что теперь в моем Javascript, вместо того, чтобы делать

somePerson.Birthday

Я должен сделать

somePerson.Birthday.DateTime 

  or

somePerson.Birthday["DateTime"]

как я могу сделать так, чтобы пользовательский конвертер возвращал прямую строку, чтобы у меня был чистый Javascript?

Ответы [ 10 ]

21 голосов
/ 21 декабря 2010

JavaScriptSerializer определенно может делать то, что вы хотите.

Можно настроить сериализацию, выполняемую JavaScriptSerializer, для любого типа, создав собственный преобразователь и зарегистрировав его в сериализаторе. Если у вас есть класс с именем Person, мы можем создать конвертер следующим образом:

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;
    }
}

И используйте это так:

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
11 голосов
/ 05 мая 2011

Вот улучшение для принятого ответа.

Использование обобщений, передача типа и использование отражения для определения свойств даты и времени.

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;
    }
}

Простое использование:

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

Надеюсь, это кому-нибудь поможет.

10 голосов
/ 28 декабря 2011

На самом деле есть хороший чистый способ сделать это, не зная типа оболочки или даже не нуждаясь в объекте оболочки.

Вы используете JavaScriptConverter для преобразования вашего объекта в Uri, который также реализует IDictionary. JavaScriptSerializer будет сериализовать это как строку.

Этот хак описан здесь:

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

4 голосов
/ 02 декабря 2009

На самом деле существует ужасный способ: создать JavaScriptConverter для класса контейнера (Person / Article / Wh независимо)

Контейнер:

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

Преобразователь:

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"));
    }
}

Счастливо ...

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);
3 голосов
/ 28 апреля 2012

Я понимаю, что уже немного поздно для ответа, но недавно я нашел действительно хорошее решение этой проблемы. Это задокументировано в этом посте на случай, если кто-то еще сочтет это полезным: http://icanmakethiswork.blogspot.co.uk/2012/04/beg-steal-or-borrow-decent-javascript.html

2 голосов
/ 28 августа 2009

ответ: вы не можете использовать JavaScriptConverter таким образом ... у него нет возможностей.

но для справки:

Как мне отформатировать дату Microsoft JSON? http://blog.stevenlevithan.com/archives/date-time-format

Если вам интересно, то, что я в итоге сделал, это добавил метод в прототип строки javascript, чтобы мне было проще в коде:

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

это по-прежнему болезненно использовать в самом корне кода, потому что вы должны постоянно вызывать dateFromJSON () везде ... что глупо.

0 голосов
/ 19 ноября 2017

У меня была похожая проблема, когда я хотел, чтобы класс SensorReading , имеющий свойства Enum 'type' и 'unit', сериализовался с именем значений Enum. (Результат по умолчанию равен 0, если Enum не имеет явного числового значения)

До того, как сериализованный результат выглядел так:

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

Что я хотел, так это:

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

В приведенном ниже примере функции я регистрирую пользовательский сериализатор 'EnumConverter <<em> SensorReading >' перед сериализацией объекта.

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


    return jss.Serialize(obj);
}

Вот общий EnumConverter

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;

    }

}

Пользовательский сериализатор объявляет, что сериализует объекты типа T, и в Serialize он зацикливает все читаемые свойства. Если свойство является Enum, оно возвращает имя вместо значения в словарь.

Это может быть распространено на другие типы свойств, не сериализующие, как мы хотим.

Я добавил отдельный тест, если в качестве пользовательского сериализатора (ов) T используется Enum. Затем вместо этого выведите имя класса Enum и его значение. Результат будет выглядеть так:

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

Это может быть лучше, если вы планируете десериализацию и хотите знать, к какому типу Enum относится значение.

0 голосов
/ 30 апреля 2015

vb.net преобразование ответа @sambomartin. Вся заслуга этого достается ему. Я просто вставил это здесь на случай, если кому-то это понадобится для vb.net.

Я также сделал это рекурсивным и добавил возможность переопределять имена свойств по умолчанию аннотациями данных 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 голосов
/ 14 октября 2010

Я знаю, это выглядит очень глупо, но пока я не нашел ничего лучше ... Я все еще смотрю, поэтому комментарии приветствуются.

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

Это просто удаляет кавычки и косые черты, поэтому на выходе получается просто Date(123456789), который, хотя технически не является литералом, воспринимается браузером как фактическое значение даты, а не строка.

В JSON это будет выглядеть так

{"myDate":Date(123456789)}

Взломать, наверное. Если бы это на самом деле реализовано в производственном коде, я бы лично обернул его, либо в метод расширения, например FormatForDates (), или в сам сериализатор, как в шаблоне декоратора ... или в этом случае в "undecorator". Я действительно скучаю по лодке, почему это так сложно. Я просто хочу сделать дату, люди! : -Р

0 голосов
/ 27 августа 2009

текст ссылки Этот пример работает

JavaScriptSerializer serializer = new JavaScriptSerializer();

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

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