【问题标题】:Can I use Json.NET to serialize objects to JavaScript constructor invocations?我可以使用 Json.NET 将对象序列化为 JavaScript 构造函数调用吗?
【发布时间】:2019-07-12 13:31:36
【问题描述】:

我需要将一个大型数据集写入网页(使用服务器端渲染),该数据集将由页面中运行的 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 的运行时组件(例如 fetchXMLHttpRequest)能够减轻使用对象文字符号(例如解析器检查数组和看到所有对象文字共享相同的属性名称集,因此可以在运行时为它们定义一个内部隐藏基类) - 但该优化不适用于这种情况,因为数据正在呈现到 SSR 网络中-page 并且我知道即使是最近的性能基准测试也没有显示针对“所有数组元素看起来相似”场景的任何解析器或编译器优化)。

有没有办法让 Json.NET 自动为我执行此操作,使用反射自动生成 JavaScript 构造函数和 JavaScript 构造函数调用?

【问题讨论】:

  • 我从未使用过它,但鉴于JsonToken 包含StartConstructorEndConstructor 的条目,我会说...也许?您是否尝试过编写自定义转换器?
  • 你能把构造函数的值生成为一个列表吗?如果是这样,您可以让构造函数接受该列表而不是(parm1, param2, param3) 等,或者通过apply(this, parameterArray) 调用它。但是,您确定属性名称的开销如此之大吗?试图忽略它们似乎是过早的优化 - 我希望压缩有效负载可以消除传输它们的“问题”。
  • 没有办法自动执行此操作,但您可以通过调用 JsonWriter.WriteStartConstructor(string name)custom JsonConverter 和稍后的 JsonWriter.WriteEndCOnstructor() 来执行此操作。有关详细信息,请参阅When using a JsonWriter, what's the purpose of WriteStartConstructor?。自定义转换器可以通过使用反射或serializer.ContractResolver 进行通用化。
  • 您的问题是,Json.NET 有没有办法自动为我执行此操作... 使用反射的通用 JsonConverter 是否足够?

标签: javascript c# serialization json.net


【解决方案1】:

没有办法完全自动执行此操作,但您可以通过调用 JsonWriter.WriteStartConstructor(string name) 和稍后调用 JsonWriter.WriteEndCOnstructor()custom JsonConverter 来执行此操作。详情请参阅When using a JsonWriter, what's the purpose of WriteStartConstructor?。自定义转换器可以通过使用 .Net 反射或 serializer.ContractResolver 返回的 Json.NET 的缓存元数据进行通用化,但如果是,则需要某种方式来确定构造函数参数顺序。

例如,假设您的类型 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
  )
]

注意事项:

演示小提琴here.

【讨论】:

  • 哇!谢谢:D(关于 Date(1233) 值,它们不是真实的 - 我从 unixtimestamp.com 复制它们,所以它们以秒为单位,而不是毫秒)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多