Могу ли я использовать Json.NET для сериализации объектов в вызовы конструктора JavaScript? - PullRequest
0 голосов
/ 19 февраля 2019

Мне нужно записать большой набор данных на веб-страницу (с использованием рендеринга на стороне сервера), который будет использоваться компонентами JavaScript, работающими на странице.Эти данные состоят из массива объектов для компонента сетки данных и компонента построения диаграмм (поэтому каждый элемент массива является строкой сетки данных).

Я бы хотел использовать конструкторы объектов JavaScript вместо объекталитералы по соображениям производительности (есть оптимизация компилятора JIT для объектов, использующих конструкторы, и они используют меньше места на проводе, потому что имена свойств опущены).Я также могу использовать нативные конструкторы Date.

Вот как я это делаю прямо сейчас:

<script type="text/javascript">

function WeeklySalesRow( weekName, date, totalSales, profit, loss, turnover, deltaSalesPercentage, etc )
{
    this.weekName = weekName;
    this.date = date;
    this.totalSales = totalSales;
    this.profit = profit;
    this.loss = loss;
    // etc
}

var weeklySalesData = [
@{
    Boolean first = true;
    foreach( WeeklySalesRow row in this.Model.WeeklySalesData ) {
        if( !first ) this.WriteLine( "," ); first = false;
        this.Write( "new WeeklySalesRow( \"{0}\", new Date({1}), {2}, {3}, {4}, etc )", row.WeekName, row.Date.ToUnixTimestamp(), row.TotalSales, row.Profit, row.Loss, row.Turnover, etc );
    }
}
];

function onDomContentLoaded( e ) {

    var chartCompoennt = ...
    chartComponent.loadData( weeklySalesData );
}

</script>

Отрисовывает так:

// [...]

var weeklySalesData = [
new WeeklySalesRow( "2018W1", new Date(1514764800), 1100, 200, 900, 50, 0.56, etc ),
new WeeklySalesRow( "2018W2", new Date(1515369600), 1200, 100, 800, 45, 0.80, etc ),
new WeeklySalesRow( "2018W3", new Date(1515974400), 1300, 50, 700, 65, 0.12, etc ),
new WeeklySalesRow( "2018W4", new Date(1516752000), 1400, 25, 600, 80, 0.45, etc ),
new WeeklySalesRow( "2018W5", new Date(1517443200), 1500, 12, 500, 90, 0.123, etc ),
// etc...
];

// [...]

Который является более кратким, чем:

var weeklySalesData = [
{ weekName: "2018W1", date: "2018-01-01", totalSales: 1100, profit: 200, loss: 900, turnover: 50, deltaSalesPercentage: 0.56, etc },
{ weekName: "2018W2", date: "2018-01-08", totalSales: 1200, profit: 100, loss: 800, turnover: 50, deltaSalesPercentage: 0.56, etc },
{ weekName: "2018W3", date: "2018-01-17", totalSales: 1300, profit: 50, loss: 700, turnover: 50, deltaSalesPercentage: 0.56, etc },
{ weekName: "2018W4", date: "2018-01-23", totalSales: 1400, profit: 25, loss: 600, turnover: 50, deltaSalesPercentage: 0.56, etc },
{ weekName: "2018W5", date: "2018-02-01", totalSales: 1500, profit: 12, loss: 500, turnover: 50, deltaSalesPercentage: 0.56, etc },
];

Я знаю, что компоненты времени выполнения, которые могут анализировать JSON напрямую (например, fetch и XMLHttpRequest), способны снизить определенный вред производительности, возникающий при использовании объекта-литеральная нотация (например, анализатор проверяет массив и видит, что все литералы объектов совместно используют один и тот же набор имен свойств и поэтому могут определять для них внутренний скрытый базовый класс во время выполнения) - но эта оптимизация не применяется в этомслучай, потому что данные выводятся на веб-страницу SSR, и я понимаю, что даже недавние тесты производительности не показывают никакой оптимизации парсера или компилятора для сценария «все элементы массива одинаковы»).

Isесть способ для Json.NET сделать это для меня автоматически, используя отражение, чтобы автоматически генерировать конструктор JavaScript и JavaScripт конструктор вызывает?

1 Ответ

0 голосов
/ 19 февраля 2019

Нет способа сделать это полностью автоматически, но вы можете сделать это с помощью custom JsonConverter, который вызывает JsonWriter.WriteStartConstructor(string name) и позже JsonWriter.WriteEndCOnstructor().См. При использовании JsonWriter, какова цель WriteStartConstructor? для получения подробной информации.Пользовательский конвертер можно сделать универсальным, используя отражение .Net или кэшированные метаданные Json.NET, возвращаемые serializer.ContractResolver, но если это так, то для определения порядка аргументов конструктора потребуется некоторый способ.

Например, скажем, ваш тип WeeklySalesData выглядит примерно так:

public class WeeklySalesData
{
    string weekName;
    DateTime date;
    decimal totalSales;

    // If WeeklySalesData had multiple constructors, you could mark the one to use as follows:
    // [JsonConstructor]
    public WeeklySalesData(string weekName, DateTime date, decimal totalSales)
    {
        this.weekName = weekName;
        this.date = date;
        this.totalSales = totalSales;
    }

    public string WeekName { get { return weekName; } }

    public DateTime Date { get { return date; } }

    public decimal TotalSales { get { return totalSales; } }
}

Обратите внимание, что у него есть параметризованный конструктор, который будет использоваться Json.NET для конструирования типа во время десериализации.Чтобы сериализовать такой тип с использованием формата конструктора, сначала введите следующий преобразователь:

public class ConstructorConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contract = serializer.ContractResolver.ResolveContract(value.GetType()) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException(string.Format("Type {0} does not correspond to a JSON object.", value.GetType()));
        // Possibly check whether JsonObjectAttribute is applied, and use JsonObjectAttribute.Title if present.
        writer.WriteStartConstructor(value.GetType().Name);
        foreach (var provider in contract.GetConstructorParameterValueProviders())
        {
            serializer.Serialize(writer, provider.GetValue(value));
        }
        writer.WriteEndConstructor();
    }
}

public static partial class JsonExtensions
{
    internal static IEnumerable<IValueProvider> GetConstructorParameterValueProviders(this JsonObjectContract contract)
    {
        return contract.CreatorParameters.Select(p => contract.GetConstructorParameterValueProvider(p)).ToArray();
    }

    internal static IValueProvider GetConstructorParameterValueProvider(this JsonObjectContract contract, JsonProperty parameter)
    {
        if (parameter.ValueProvider != null)
            return parameter.ValueProvider;
        var property = contract.Properties.GetClosestMatchProperty(parameter.PropertyName);
        var provider = property == null ? null : property.ValueProvider;
        if (provider == null)
            throw new JsonSerializationException(string.Format("Cannot get IValueProvider for {0}", parameter));
        return provider;
    }
}

Затем выполните сериализацию с использованием следующих преобразователей:

var data = new WeeklySalesData("2018W1", new DateTime(2019, 2, 15, 0, 0, 0, DateTimeKind.Utc), 1100);
var settings = new JsonSerializerSettings
{
    Converters = { new JavaScriptDateTimeConverter(), new ConstructorConverter<WeeklySalesData>() },
};

var json = JsonConvert.SerializeObject(new [] { data }, Formatting.Indented, settings);

В результате:

[
  new WeeklySalesData(
    "2018W1",
    new Date(
      1550188800000
    ),
    1100.0
  )
]

Примечания:

Демонстрационная скрипка здесь .

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