Skip to content

Normalizing casing for property names in JsonException.Path helps deserialization performance #30789

@steveharter

Description

@steveharter

Creating this issue to discuss feasibility of getting this into 5.0. cc @pranavkm @ahsonkhan @rynowak

If we remove support for using the literal JSON value for JsonException.Path we can get a ~5% end-to-end performance improvement during deserialization when using options.CaseInsensitivePropertyNames=true (which is the default for ASP.NET). Allocations are also reduced by ~30% (depends on property name lengths).

UPDATE: recent measurements for a very simple case show up to 30% improvement, not 5%. Todo: need to quantify this here.

This means that when case insensitivity is on, the value of the JsonException.Path property may not exactly match the property name casing in the JSON (the value is always correct; just the casing can be off).

There are three ways the JSON property name is specified:

  1. The object's CLR property reflected name (default case).
  2. The value from [JsonPropertyName] attribute applied to a property.
  3. The naming policy from options.PropertyNamingPolicy such as camel-casing.

So currently if there exists JSON like {"myProp:1"} against a property named MyProp (either through case 1, 2 or 3 above) then when case-insensitivity is on the JsonException.Path will be "$.myProp".

However, using the raw JSON value comes at a cost for case insensitivity. Instead if we just use the value obtained from case 1, 2 or 3 (and not the actual JSON) then we get the perf improvement -- e.g. path would be "$.MyProp" instead of {"myProp:1"} (again this would only occur when case insensitivity is on and does not match the actual property name).

Current:
|                   Method |     Mean |    Error |   StdDev |   Median |      Min |      Max | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|------------------------- |---------:|---------:|---------:|---------:|---------:|---------:|------------:|------------:|------------:|--------------------:|
|    DeserializeFromString | 662.8 ns | 3.631 ns | 3.218 ns | 662.8 ns | 658.7 ns | 670.2 ns |      0.0611 |           - |           - |               384 B |
| DeserializeFromUtf8Bytes | 614.0 ns | 2.882 ns | 2.696 ns | 614.6 ns | 609.1 ns | 618.9 ns |      0.0420 |           - |           - |               272 B |
|    DeserializeFromStream | 915.3 ns | 3.769 ns | 3.341 ns | 916.6 ns | 908.8 ns | 918.4 ns |      0.0513 |           - |           - |               344 B |

After:
|    DeserializeFromString | 637.5 ns | 3.702 ns | 3.463 ns | 636.6 ns | 632.2 ns | 643.7 ns |      0.0436 |           - |           - |               280 B |
| DeserializeFromUtf8Bytes | 586.1 ns | 4.028 ns | 3.768 ns | 585.3 ns | 579.7 ns | 592.6 ns |      0.0259 |           - |           - |               168 B |
|    DeserializeFromStream | 913.7 ns | 4.604 ns | 4.307 ns | 912.7 ns | 907.1 ns | 921.5 ns |      0.0369 |           - |           - |               240 B |

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions