cf.completefrontend

Write the codeMedium

Write unflattenObject(object) to rebuild a nested object from dotted keys.

Before you start

What's prototype pollution?

Every plain object inherits from one shared object, Object.prototype: it's where methods like toString and hasOwnProperty come from. Prototype pollution is a security bug in which input from outside the program, such as a query string, a form field's name, or a JSON request body, gets code to write a property onto Object.prototype. From then on, every object in the program seems to have that property.

The way in is a few special key names. On a plain object, obj.__proto__ isn't an ordinary property: it's a getter and setter, inherited from Object.prototype, that reads or replaces the object's prototype. So obj["__proto__"] is Object.prototype itself, and a property written through it is written for everyone:

const settings = {};
const key = "__proto__"; // imagine it came from a query string
 
settings[key].isAdmin = true; // settings.__proto__ is Object.prototype
({}).isAdmin;                 // true: every object has it now
 
const user = { name: "Ada" };
user.isAdmin;                 // true, though user never had the property
 
delete Object.prototype.isAdmin; // clean up

constructor and prototype lead to the same place: obj.constructor is Object, and Object.prototype is the shared prototype. Here's where a function that sets a value at a dotted path would write, if it followed each key as given:

PathFollowing it reachesThe value lands on
user.isAdminobj.user, an ordinary nested objectobj.user, as intended
__proto__.isAdminobj.__proto__, which is Object.prototypeObject.prototype, so every object
constructor.prototype.isAdminobj.constructor, which is Object, then Object.prototypeObject.prototype, so every object

Why it's a security bug: code often checks a property that's normally missing, like if (user.isAdmin), and after pollution every object passes. Replacing an inherited method, such as toString, can also make code all over the app throw. Bugs like this have been found and fixed in popular libraries, Lodash and jQuery among them.

One more detail. Assigning obj["__proto__"] = value on a plain object doesn't create a key named __proto__: it replaces obj's prototype with value, or does nothing if value isn't an object or null. JSON.parse, though, does create an ordinary key named __proto__, so parsed JSON can carry one into code that copies or merges keys:

const parsed = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.keys(parsed); // ["__proto__"]: a real own key, harmless so far
 
const target = {};
target["__proto__"] = parsed["__proto__"]; // replaces target's prototype
Object.keys(target); // []: no key was created
target.isAdmin;      // true, inherited from its new prototype

{ "a.b": 1, "a.c.0": 2, "a.c.1": 3 } becomes { a: { b: 1, c: [2, 3] } }. Process the entries in order.

When a step along a path is missing or isn't an object, replace it with an array if the next key is an array index (digits only), or an object otherwise.

Skip any entry whose path contains __proto__, constructor, or prototype.

Write this
function unflattenObject(object) {
  // your code
}
 
// unflattenObject({ "user.name": "Ada", "user.tags.0": "math", active: true });
// // { user: { name: "Ada", tags: ["math"] }, active: true }
// unflattenObject({ a: 1, "a.b": 2 });
// // { a: { b: 2 } }: the second entry replaces the 1
// unflattenObject({ "__proto__.isAdmin": true, ok: 1 });
// // { ok: 1 }: the first entry is skipped

Solve it in the editor, with tests

The answer

Write it yourself first. Then compare it with a reference solution and the checks it passes.

Checking your sign-in…