Skip to content

[ecmascript,vrotsc] Add shim for Object.entries()#1083

Merged
VenelinBakalov merged 1 commit intovmware:mainfrom
Indy-rbo:feature/add-object.entries-shim
Mar 30, 2026
Merged

[ecmascript,vrotsc] Add shim for Object.entries()#1083
VenelinBakalov merged 1 commit intovmware:mainfrom
Indy-rbo:feature/add-object.entries-shim

Conversation

@Indy-rbo
Copy link
Copy Markdown
Contributor

Description

Added Shim for Object.entries.

There already is a Shim for Object.values and Object.keys is natively supported.
As it was suggested to add this missing shim to known issues in #1073 I wanted to see how easy it was to add it.

Don't think we need to add to any tsconfig lib definitions as ES2017.Object was already added in #1073

Checklist

  • I have added relevant error handling and logging messages to help troubleshooting
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation, relevant usage information (if applicable)
  • I have updated the PR title with affected component, related issue number and a short summary of the changes introduced
  • I have added labels for implementation kind (kind/) and version type (version/)
  • I have tested against live environment, if applicable
  • I have my changes rebased and squashed to the minimal number of relevant commits. Notice: don't squash all commits

Testing

  • Updated tests in vrotsc/e2e to test the Object.entries transpilation.

There are no specific unit test files for Shims, but wrote tests locally to see if the function matched the behavior of the actual implementation. Based them on MDN examples and made an LLM generate extra edge cases.

Toggle below for the test cases, in case you wish to verify or want to adopt them later.

Local tests
// Object.entries tests
import test, { describe } from "node:test";
import assert from "node:assert/strict";

function objectEntries(target: any): [string, any][] {
  return Object.keys(target).map(key => [key, target[key]]);
}

describe("objectEntries", () => {
  test("MDN example: using Object.entries()", () => {
    const obj = { foo: "bar", baz: 42 };
    assert.deepEqual(objectEntries(obj), [["foo", "bar"], ["baz", 42]]);
    assert.deepEqual(objectEntries(obj), Object.entries(obj));
  });

  test("MDN example: array-like object", () => {
    const arrayLike: any = { 0: "a", 1: "b", 2: "c" };
    assert.deepEqual(objectEntries(arrayLike), [["0", "a"], ["1", "b"], ["2", "c"]]);
    assert.deepEqual(objectEntries(arrayLike), Object.entries(arrayLike));
  });

  test("MDN example: random integer key order", () => {
    const randomKeyOrder: any = { 100: "a", 2: "b", 7: "c" };
    assert.deepEqual(objectEntries(randomKeyOrder), [["2", "b"], ["7", "c"], ["100", "a"]]);
    assert.deepEqual(objectEntries(randomKeyOrder), Object.entries(randomKeyOrder));
  });

  test("MDN example: non-enumerable properties are excluded", () => {
    const myObj = Object.create(
      {},
      {
        getFoo: {
          value() {
            return (this as any).foo;
          },
          enumerable: false,
        },
      },
    ) as any;
    myObj.foo = "bar";
    assert.deepEqual(objectEntries(myObj), [["foo", "bar"]]);
    assert.deepEqual(objectEntries(myObj), Object.entries(myObj));
  });

  test("MDN example: converting an Object to a Map", () => {
    const obj = { foo: "bar", baz: 42 };
    const map = new Map(objectEntries(obj));
    assert.equal(map.get("foo"), "bar");
    assert.equal(map.get("baz"), 42);
  });

  test("should return entries of an object", () => {
    const obj = { a: 1, b: "test", c: true };
    const entries = objectEntries(obj);
    assert.deepEqual(entries, [["a", 1], ["b", "test"], ["c", true]]);
    assert.deepEqual(entries, Object.entries(obj));
  });

  test("should return an empty array for an empty object", () => {
    const obj = {};
    const entries = objectEntries(obj);
    assert.deepEqual(entries, []);
    assert.deepEqual(entries, Object.entries(obj));
  });

  test("should only include own properties", () => {
    const parent = { inherited: "inherited" };
    const obj = Object.create(parent);
    obj.own = "own";
    const entries = objectEntries(obj);
    assert.deepEqual(entries, [["own", "own"]]);
    assert.deepEqual(entries, Object.entries(obj));
  });

  test("should handle non-string keys", () => {
    const obj = { 1: "one", true: "boolean", null: "null" };
    const entries = objectEntries(obj);
    assert.deepEqual(entries, [["1", "one"], ["true", "boolean"], ["null", "null"]]);
    assert.deepEqual(entries, Object.entries(obj));
  });

  test("should match Object.entries for many shapes", () => {
    const samples: any[] = [
      { a: 1 },
      { a: 1, b: undefined, c: null },
      Object.create(null),
      [],
      ["x", "y"],
      new Date(0),
      function f() { /* empty */ },
      /re/, // RegExp
      new Map(),
      new Set(),
    ];

    for (const sample of samples) {
      assert.deepEqual(objectEntries(sample), Object.entries(sample));
    }
  });

  test("should throw the same way for null and undefined", () => {
    assert.throws(() => objectEntries(null));
    assert.throws(() => objectEntries(undefined));

    // Ensure the native method also throws (behavior we're matching)
    assert.throws(() => Object.entries(null as any));
    assert.throws(() => Object.entries(undefined as any));
  });

  test("should match Object.entries for primitives (boxing behavior)", () => {
    const primitives: any[] = [
      0,
      1,
      NaN,
      Infinity,
      true,
      false,
      "",
      "abc",
      Symbol("s"),
      10n,
    ];
    for (const value of primitives) {
      assert.deepEqual(objectEntries(value), Object.entries(value));
    }
  });

  test("should return string index entries for strings", () => {
    const s = "hello";
    assert.deepEqual(objectEntries(s), [["0", "h"], ["1", "e"], ["2", "l"], ["3", "l"], ["4", "o"]]);
    assert.deepEqual(objectEntries(s), Object.entries(s));
  });

  test("should not include symbol keys (Object.entries ignores symbols)", () => {
    const sym = Symbol("k");
    const obj: any = { a: 1 };
    obj[sym] = 2;
    assert.deepEqual(objectEntries(obj), [["a", 1]]);
    assert.deepEqual(objectEntries(obj), Object.entries(obj));
  });

  test("should ignore non-enumerable properties", () => {
    const obj: any = { a: 1 };
    Object.defineProperty(obj, "hidden", { value: 123, enumerable: false });
    assert.deepEqual(objectEntries(obj), [["a", 1]]);
    assert.deepEqual(objectEntries(obj), Object.entries(obj));
  });

  test("should respect property ordering rules (integer-like keys first)", () => {
    const obj: any = {};
    obj.b = "b";
    obj["2"] = "two";
    obj.a = "a";
    obj["1"] = "one";
    obj["01"] = "zero-one";
    obj["10"] = "ten";
    // Native order: array-index keys in ascending order, then other string keys in insertion order.
    assert.deepEqual(objectEntries(obj), Object.entries(obj));
    assert.deepEqual(objectEntries(obj).map(([k]) => k), ["1", "2", "10", "b", "a", "01"]);
  });

  test("order should match a for...in loop for own enumerable props", () => {
    const proto: any = { inherited: "nope" };
    const obj: any = Object.create(proto);
    obj.b = 2;
    obj["2"] = "two";
    obj.a = 1;
    obj["1"] = "one";
    Object.defineProperty(obj, "hidden", { value: 0, enumerable: false });

    const forInOwn: string[] = [];
    for (const k in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, k)) {
        forInOwn.push(k);
      }
    }

    assert.deepEqual(objectEntries(obj).map(([k]) => k), forInOwn);
    assert.deepEqual(objectEntries(obj), Object.entries(obj));
  });

  test("should call getters and capture their returned values", () => {
    let calls = 0;
    const obj: any = {
      get a() {
        calls++;
        return 42;
      },
      b: 1,
    };

    const ours = objectEntries(obj);
    const native = Object.entries(obj);
    assert.deepEqual(ours, native);
    // Both implementations must read the getter at least once.
    assert.ok(calls > 0);
  });

  test("should work with arrays (indexes only, plus extra enumerable props)", () => {
    const arr: any = ["x", , "z"]; // hole at index 1
    arr.extra = "e";
    assert.deepEqual(objectEntries(arr), Object.entries(arr));
    // Holes are skipped because there is no own property for that index.
    assert.deepEqual(objectEntries(arr), [["0", "x"], ["2", "z"], ["extra", "e"]]);
  });

  test("should work with functions (own enumerable props only)", () => {
    function f() { /* noop */ }
    (f as any).x = 1;
    Object.defineProperty(f, "hidden", { value: 2, enumerable: false });
    assert.deepEqual(objectEntries(f), Object.entries(f));
    assert.deepEqual(objectEntries(f), [["x", 1]]);
  });

  test("should reflect Proxy traps same as Object.entries", () => {
    const target: any = { a: 1, b: 2 };
    const proxy = new Proxy(target, {
      ownKeys(t) {
        // Reverse key order deliberately
        return Reflect.ownKeys(t).filter(k => typeof k === "string").reverse();
      },
      get(t, p, r) {
        return Reflect.get(t, p, r);
      },
      getOwnPropertyDescriptor(t, p) {
        return Object.getOwnPropertyDescriptor(t, p);
      },
    });

    assert.deepEqual(objectEntries(proxy), Object.entries(proxy));
  });

  test("should not include inherited enumerable props", () => {
    const parent: any = { p: "parent" };
    const child: any = Object.create(parent);
    child.c = "child";
    assert.deepEqual(objectEntries(child), Object.entries(child));
    assert.deepEqual(objectEntries(child), [["c", "child"]]);
  });

  test("should include enumerable properties with undefined values", () => {
    const obj = { a: undefined, b: 1 };
    assert.deepEqual(objectEntries(obj), Object.entries(obj));
    assert.deepEqual(objectEntries(obj), [["a", undefined], ["b", 1]]);
  });

  test("should include properties named like built-ins as normal keys", () => {
    const obj: any = {
      toString: "x",
      constructor: "y",
      hasOwnProperty: "z",
    };
    assert.deepEqual(objectEntries(obj), Object.entries(obj));
  });
})

Which gave these results:

image

Live testing

  1. Create an action / workflow that has the following code:
(function objectEntries (): void {
  const obj = { foo: 'bar', baz: 42 }
  const entries = Object.entries(obj)

  System.log(JSON.stringify(entries))

  for (const [key, value] of entries) {
    System.log(`Key: ${key}, Value: ${value}`)
  }
})

Which compiles to:

var __global = System.getContext() || (function () {
    return this;
}).call(null);
var VROES = __global.__VROES || (__global.__VROES = System.getModule("com.vmware.pscoe.library.ecmascript").VROES());
var obj = { foo: 'bar', baz: 42 };
var entries = VROES.Shims.objectEntries(obj);
System.log(JSON.stringify(entries));
for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) {
    var _a = entries_1[_i], key = _a[0], value = _a[1];
    System.log("Key: ".concat(key, ", Value: ").concat(value));
}
  1. Run the action, the logs should show:
[["foo","bar"],["baz",42]]
Key: foo, Value: bar
Key: baz, Value: 42

Test Screenshot

image

Release Notes

Added Shim for Object.entries.

Signed-off-by: Indy Vierboom <Indy.Vierboom@rabobank.nl>
@Indy-rbo Indy-rbo requested a review from a team as a code owner March 27, 2026 21:17
@VenelinBakalov VenelinBakalov changed the title [ecmascript,vrotsc] add shim for Object.entries [ecmascript,vrotsc] Add shim for Object.entries Mar 30, 2026
@VenelinBakalov VenelinBakalov changed the title [ecmascript,vrotsc] Add shim for Object.entries [ecmascript,vrotsc] Add shim for Object.entries() Mar 30, 2026
@VenelinBakalov VenelinBakalov added lang/typescript Related to typescript code area/vrotsc Relates to `vrotsc` module version/minor Introduces a non-breaking feature or change kind/feature New Feature to the project labels Mar 30, 2026
@VenelinBakalov VenelinBakalov self-assigned this Mar 30, 2026
@VenelinBakalov
Copy link
Copy Markdown
Collaborator

@Indy-rbo thank you! looks good!

@VenelinBakalov VenelinBakalov merged commit dc8d484 into vmware:main Mar 30, 2026
8 of 9 checks passed
@Indy-rbo Indy-rbo deleted the feature/add-object.entries-shim branch March 30, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/vrotsc Relates to `vrotsc` module kind/feature New Feature to the project lang/typescript Related to typescript code version/minor Introduces a non-breaking feature or change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants