mirror of https://github.com/Squidex/squidex.git
Browse Source
JsonMapper built every JSON object it projects into script as an instance of a private ObjectInstance subclass that existed only to be instantiable. A host subclass can never carry the engine's shape-mode storage flag, so each of those objects - all of ctx.data's leaf objects, and every JsonValue var - sat permanently outside the own-property inline caches, and a script reading the same property across a batch of content items re-resolved it every time. JsObject.CreateFromEntries builds the same object through the hidden class machinery instead: repeated calls presenting the same key sequence, which every content item of one schema does, share an interned hidden class, so those reads stay monomorphic. The result is documented as indistinguishable from the equivalent object literal - same own key order, same configurable/enumerable/writable data properties - and anything the representation cannot express (a digit-leading key, a very wide object) falls back to the ordinary dictionary representation rather than to different behaviour. That fallback is silent, which is why the shaping is asserted rather than assumed: Engine.Advanced.HasSharedShape is the supported predicate for it, and JsonMapperTests pins that the projected object and its nested objects answer true. Building them as a host subclass again would fail that test. Three smaller fixes in the same file: - the reverse direction allocated a string key per array element (a.Get(i.ToString(...))); the indexed accessor reads the dense backing directly and keeps the prototype walk for a modified array; - JsNumber.Create reuses cached instances for small integers where new JsNumber always allocated; - JsString.Create, public since 4.15.3, interns the empty and single character strings where new JsString always allocated. Pinned by the projection tests added first - own key order, JSON.stringify, for..in, mutation including delete and add, and the round trip back to JsonValue - which pass before and after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016uV6H9cTntzsoKiaJRBn4fpull/1326/head
2 changed files with 70 additions and 14 deletions
@ -0,0 +1,53 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Jint; |
|||
using Jint.Native.Object; |
|||
using Squidex.Domain.Apps.Core.Scripting.Internal; |
|||
using Squidex.Infrastructure.Json.Objects; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Operations.Scripting; |
|||
|
|||
public class JsonMapperTests |
|||
{ |
|||
[Fact] |
|||
public void Should_map_json_objects_into_a_shared_shape() |
|||
{ |
|||
var engine = new Engine(o => o.Strict()); |
|||
|
|||
var mapped = (ObjectInstance)JsonMapper.Map(CreateJson(), engine); |
|||
var nested = (ObjectInstance)mapped.Get("nested"); |
|||
|
|||
// A shared shape is what keeps a script reading a batch of content items monomorphic. It is a
|
|||
// performance property and never a correctness one, but it is silent when it regresses: building
|
|||
// these objects as a host ObjectInstance subclass again would put them back in the per-object
|
|||
// dictionary with no test noticing.
|
|||
Assert.True(engine.Advanced.HasSharedShape(mapped)); |
|||
Assert.True(engine.Advanced.HasSharedShape(nested)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_share_the_shape_between_objects_of_the_same_shape() |
|||
{ |
|||
var engine = new Engine(o => o.Strict()); |
|||
|
|||
var first = (ObjectInstance)JsonMapper.Map(CreateJson(), engine); |
|||
var second = (ObjectInstance)JsonMapper.Map(CreateJson(), engine); |
|||
|
|||
Assert.True(engine.Advanced.HasSharedShape(first)); |
|||
Assert.True(engine.Advanced.HasSharedShape(second)); |
|||
} |
|||
|
|||
private static JsonValue CreateJson() |
|||
{ |
|||
return JsonValue.Create( |
|||
new JsonObject() |
|||
.Add("name", JsonValue.Create("squidex")) |
|||
.Add("count", JsonValue.Create(3)) |
|||
.Add("nested", JsonValue.Create(new JsonObject().Add("flag", JsonValue.True)))); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue