我可以使用Json.NET将对象序列化为JavaScript构造函数调用吗?
我需要将一个大数据集写入一个网页(使用服务器端呈现),该网页中运行的JavaScript组件将使用该数据集。该数据由数据网格组件和图表组件的对象数组组成(因此每个数组元素都是数据网格行) 出于性能原因,我希望使用JavaScript对象构造函数而不是对象文本(有使用构造函数的对象的JIT编译器优化,并且由于省略了属性名,因此它们使用的空间更少)。我也可以在本地使用我可以使用Json.NET将对象序列化为JavaScript构造函数调用吗?,javascript,c#,serialization,json.net,Javascript,C#,Serialization,Json.net,我需要将一个大数据集写入一个网页(使用服务器端呈现),该网页中运行的JavaScript组件将使用该数据集。该数据由数据网格组件和图表组件的对象数组组成(因此每个数组元素都是数据网格行) 出于性能原因,我希望使用JavaScript对象构造函数而不是对象文本(有使用构造函数的对象的JIT编译器优化,并且由于省略了属性名,因此它们使用的空间更少)。我也可以在本地使用Date构造函数 以下是我现在的做法: <script type="text/javascript"> function
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 = [
{ 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网页中,我知道即使是最新的性能基准也没有显示任何针对“所有数组元素看起来都一样”场景的解析器或编译器优化)
有没有一种方法可以让Json.NET自动为我实现这一点,即使用反射自动生成JavaScript构造函数和JavaScript构造函数调用?没有完全自动的方法,但您可以使用一个调用该函数的函数或更高版本来实现这一点。有关详细信息,请参阅。通过使用.Net反射或返回的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;
}
}
注:
- Newtonsoft从1970年1月1日00:00:00 UTC开始,以毫秒为单位序列化
构造函数,这与。您似乎正在使用不同的单位进行序列化,可能是秒而不是毫秒DateTime
- 如果您的类型没有参数化构造函数,则可以使用其他机制(如或)指定JavaScript构造函数参数顺序。然后,可以使用与
ObjectToArrayConverter.WriteJson()相同的算法从集合中按顺序获取构造函数参数
- 我没有尝试实现反序列化,因为它没有被请求
演示小提琴。我从未使用过它,但考虑到它包括
StartConstructor
和EndConstructor
的条目,我想说。。。大概您是否尝试过编写自定义转换器?是否可以将构造函数的值作为列表生成?如果是这样,您可以让构造函数接受该列表,而不是(parm1,param2,param3)
等,或者通过apply(this,parameterArray)
调用它。但是,您确定属性名称的开销很大吗?试图忽略它们似乎是过早的优化—我希望gzip压缩有效负载可以消除传输它们时的“问题”。没有办法自动完成这项工作,但您可以使用调用的函数和稍后的JsonWriter.writeedConstructor()
来完成。有关详细信息,请参阅。自定义转换器可以通过使用反射或serializer.ContractResolver
实现通用化。您的问题是,Json.NET是否有办法自动为我实现这一点。。。使用反射的通用JsonConverter
足够吗?哇!谢谢:D(关于Date(1233)
值,它们不是真实的-我从unixtimestamp.com复制了它们,所以它们以秒为单位,而不是以毫秒为单位)。
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
)
]