Files
thrift/lib/nodejs/test/MapKeyTest.thrift
Jens GeyerandClaude Opus 5 ef3ac76834 Store a wire-supplied map key as an own member in generated JavaScript
Client: js,nodejs,ts

generate_deserialize_map_element emitted map[key] = value with key read from
the peer. On a plain object "__proto__" is not an ordinary member name: the
assignment runs the setter inherited from Object.prototype, which swaps the
object's prototype when the value is an object and silently discards the entry
when it is not. Neither stores the pair.

Measured on generated code before the change, over a map<string, struct>: a
map entry whose key is "__proto__" left the decoded map with no own keys and
its prototype replaced by the value, so a member the application never received
resolved to something the peer chose. For map<string, string> the entry simply
vanished.

defineProperty stores the pair in both cases, keeps Object.prototype, and
leaves every other key on the plain assignment. The guard is emitted for every
map, not only those whose values can be objects, because the silent-drop half
applies to the rest.

Three alternatives were built out as generated code and round-tripped before
settling on this one. Object.create(null) is not viable: the generated writer
calls this.<field>.hasOwnProperty(kiter), which such an object does not have,
and Thrift.objectLength() returns 0 for it, so a decoded map could not be sent
on. A Map changes the decoded type and breaks every caller that indexes it.
Skipping the key discards data a peer may legitimately send.

The generator has no mode switch here, so this covers browser js, js:node,
js:es6, js:ts, js:jquery and the esm variant alike; all eight were generated,
syntax-checked and confirmed to carry the guard.

Stacked on the json_parse change, and it needs it: under TJSONProtocol the map
arrives as an object json_parse built, so without that fix the key has already
gone into the prototype before any generated code runs. Seven of the 54 cases
below fail on this commit alone for exactly that reason, and pass on the pair.

lib/nodejs/test/MapKeyTest.thrift and lib/nodejs/test/map_keys.test.js, written
first: 54 cases over the binary, compact and JSON protocols, 27 failing before
the change. The IDL sits with the test that uses it rather than in test/, which
is the cross-language suite: nothing outside lib/nodejs generates from it, and
lib/nodejs/Makefile.am already ships test/ wholesale, so it needs no EXTRA_DIST
entry of its own the way test/Makefile.am does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 00:50:54 +02:00

34 lines
1.2 KiB
Thrift

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// Maps whose keys come off the wire, in the JavaScript generator's output. The
// value types differ on purpose: a generated map is a plain object, and what a
// member name does to one depends on what is being stored under it.
struct MapValue {
1: string s
}
struct MapKeyHolder {
1: map<string, MapValue> structValues
2: map<string, string> stringValues
3: map<string, list<string>> listValues
4: map<string, map<string, string>> mapValues
}