Repository navigation
v2.13.0: faster instantiation from a Map (JSON entity decoding) - #61
Merged
Merged
Conversation
- createInstanceWithConstructor: a constructor that can be called directly is also called with values revived by a fieldValueResolver (once per parameter), instead of through MethodInvocation. Unresolved values and resolvers that throw still take the MethodInvocation path. - The fields-to-keys resolution, best constructors and fields to set are kept for the last map shape (same keys, order and FieldNameResolver) of each class. FieldNameResolver: documented that it must depend only on the field name and the map keys. - createInstanceFromMap: a static direct path for a map of the last shape with a single constructor (static helpers: no implicit parameter checks in dart2js). A constructor error is reported as before, without a second call. - JsonDecoder: a decoded Map<String, dynamic> is no longer copied. - JsonEntityCacheSimple.getEntityID: the ID field is found once per type. - ClassReflection.getField(name, obj): no FieldReflection per call. Decoding 1000 users (bones_api benchmark), us per user: JS (dart2js -O1 --minify) 27.4 -> 11.8, Wasm 6.5 -> 3.7, VM 7.5 -> 3.9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #61 +/- ##
==========================================
+ Coverage 93.55% 93.65% +0.10%
==========================================
Files 14 14
Lines 6544 6652 +108
==========================================
+ Hits 6122 6230 +108
Misses 422 422
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
When the fieldValueResolver left a value unresolved on the createInstanceFromMap direct path, it fell back to createInstanceWithBestConstructor, which tried the revived direct call again before the MethodInvocation path: the resolver ran over every parameter one more time (decoding nested entities again). It now goes straight to the MethodInvocation path (directUnresolved). Tests (edge cases): reviver call counts on the unresolved path (8 -> 6), a revived value for an absent parameter, an entity list with a null, the same class created inside a resolver (cached shape replaced mid-call), disposeCache between maps, getBestConstructorsForMap with other options in between, JsonDecoder with Map<String, dynamic> (not modified) and other key types, lists with nulls and mixed shapes, and getEntityID by the first int field or without an ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Faster
createInstanceFromMap/JsonDecoderentity decoding, mostly for code compiled to JavaScript (the browser), and also on Wasm and the VM. The created instances are the same.Changes
fieldValueResolver. A constructor that can be called directly (canInvokeFromResolvedMap) is now also called with the values revived by afieldValueResolver, once per parameter, instead of throughMethodInvocation(_revivedMapArguments). Unresolved values (a non-null value revived tonull, or an entity list with anull) and resolvers that throw still take theMethodInvocationpath, which resolves them again and reports them as before.FieldNameResolver), and reused for the next maps of that shape.FieldNameResolvernow documents that its result must depend only on the field name and the map keys.createInstanceFromMapdirect path: for a map of the last shape with a single constructor, the constructor is called without the intermediate calls. A constructor error is reported as before, without a second call. The helpers are static, because dart2js (without--omit-implicit-checks) checks the parameter types of instance methods on every call, and of static functions it doesn't.JsonDecoder: a decoded JSON object (Map<String, dynamic>) is no longer copied before decoding (is Map<String, Object>wasfalsefor it).JsonEntityCacheSimple.getEntityID: the ID field is found once per type.ClassReflection.getField(name, obj): noFieldReflectionis allocated per call.Benchmark
bones_api'sbenchmark/db_users/bin/json_decode_benchmark.dartdecodes a JSON list of 1000 users (each with an address and 2 roles) as a module proxy does. µs per user:-O1 --minify, thewebdev build --releasedefault)-O2 --minify)These include the
bones_apichanges in its companion PR. The reflection_factory part is most of it, about −45% in JS.Tests
581 pass (
dart test). The instantiation and JSON tests also pass compiled to JS (-p node). New tests cover:getFieldof another objectgetEntityIDThe existing equivalence helpers now also compare against the legacy
MethodInvocationpath with a reviver.v2.13.0: version bumped, generated code regenerated (version lines only), CHANGELOG added.
🤖 Generated with Claude Code