-
Notifications
You must be signed in to change notification settings - Fork 97
/
JsonSizeAnalyzer.cs
128 lines (117 loc) · 5.14 KB
/
JsonSizeAnalyzer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using DotVVM.Framework.Utils;
using DotVVM.Framework.ViewModel.Serialization;
using FastExpressionCompiler;
using Newtonsoft.Json.Linq;
namespace DotVVM.Framework.Diagnostics
{
/// <summary> Computes the inclusive and exclusive size of each JSON property. </summary>
public class JsonSizeAnalyzer
{
readonly IViewModelSerializationMapper viewModelMapper;
public JsonSizeAnalyzer(IViewModelSerializationMapper viewModelMapper)
{
this.viewModelMapper = viewModelMapper;
}
/// <summary> Computes the inclusive and exclusive size of each JSON property. </summary>
public JsonSizeProfile Analyze(JObject json)
{
Dictionary<string, ClassSizeProfile> results = new();
// returns the length of the token. Recursively calls itself for arrays and objects.
AtomicSizeProfile analyzeToken(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
return new (InclusiveSize: analyzeObject((JObject)token), ExclusiveSize: 2);
case JTokenType.Array: {
var r = new AtomicSizeProfile(0);
foreach (var item in (JArray)token)
{
r += analyzeToken(item);
}
return r;
}
case JTokenType.String:
return new (((string)token).Length + 2);
case JTokenType.Integer:
// This should be the same as token.ToString().Length, but I didn't want to allocate the string unnecesarily
return new((int)Math.Log10(Math.Abs((long)token) + 1) + 1);
case JTokenType.Float:
return new(((double)token).ToString().Length);
case JTokenType.Boolean:
return new(((bool)token) ? 4 : 5);
case JTokenType.Null:
return new(4);
case JTokenType.Guid:
return new(36 + 2);
case JTokenType.Date:
return new(23 + 2);
default:
Debug.Assert(false, $"Unexpected token type {token.Type}");
return new(token.ToString().Length);
}
}
int analyzeObject(JObject j)
{
var type = ((string?)j.Property("$type")?.Value)?.Apply(viewModelMapper.GetMapByTypeId);
var typeName = type?.Type.ToCode(stripNamespace: true) ?? "UnknownType";
var props = new Dictionary<string, AtomicSizeProfile>();
var totalSize = new AtomicSizeProfile(0);
foreach (var prop in j.Properties())
{
var propSize = analyzeToken(prop.Value);
props[prop.Name] = propSize;
totalSize += propSize;
totalSize += 4 + prop.Name.Length; // 2 for the quotes, 1 for :, 1 for ,
}
var classSize = new ClassSizeProfile(totalSize, props);
if (results.TryGetValue(typeName, out var existing))
{
results[typeName] = existing + classSize;
}
else
{
results[typeName] = classSize;
}
return totalSize.InclusiveSize;
}
var totalSize = analyzeObject(json);
return new JsonSizeProfile(results, totalSize);
}
public record JsonSizeProfile(
Dictionary<string, ClassSizeProfile> Classes,
int TotalSize
);
public record ClassSizeProfile(
AtomicSizeProfile Size,
Dictionary<string, AtomicSizeProfile> Properties,
int Count = 1
) {
public static ClassSizeProfile operator +(ClassSizeProfile a, ClassSizeProfile b)
{
var props = new Dictionary<string, AtomicSizeProfile>(a.Properties);
foreach (var prop in b.Properties)
{
props[prop.Key] = props.GetValueOrDefault(prop.Key) + prop.Value;
}
return new(
a.Size + b.Size,
props,
a.Count + b.Count
);
}
}
public record struct AtomicSizeProfile(
int InclusiveSize,
int ExclusiveSize
) {
public AtomicSizeProfile(int exclusiveSize): this(exclusiveSize, exclusiveSize) { }
public static AtomicSizeProfile operator +(AtomicSizeProfile a, AtomicSizeProfile b) => new AtomicSizeProfile(a.InclusiveSize + b.InclusiveSize, a.ExclusiveSize + b.ExclusiveSize);
public static AtomicSizeProfile operator +(AtomicSizeProfile a, int c) => new AtomicSizeProfile(a.InclusiveSize + c, a.ExclusiveSize + c);
}
}
}