Going Deep with XS: deepEqual and structuredClone

Comparing and copying JavaScript objects are fundamental operations that are remarkably complex to implement correctly and efficiently. Because these operations are not built into the JavaScript language, various solutions have arisen to make life easier for developers. Web browsers provide the structuredClone function to make a deep copy of an object. Node.js provides deepEqual assertions to perform deep comparison of objects and the is-equal module provides a faster implementation as an npm module. Embedded developers who need these operations have had to create their own ad-hoc solutions. Until now.

The Moddable SDK now includes structuredClone and deepEqual functions that emulate the behaviors of these functions in web browsers and Node.js. Read on to learn how Moddable made these powerful functions fast on embedded devices while staying compatible with the implementations on the web platform.

Fast. And Small.

To deliver the optimal performance on relatively slow embedded devices, the Moddable SDK implementations of deepEqual and structuredClone are written in C and compiled to native machine code. In addition, they are implemented using the internal data structures of the XS JavaScript engine. This combination allows them to be as fast as possible.

As an added benefit, these implementations are also quite small. Despite providing very complex behaviors, the implementations of both deepEqual and structuredClone are just a few KB of code each (the exact size varies by target CPU, compiler, etc). This small code footprint makes them practical to include in projects that require the functionality.

Compatible with the Web

Compatibility with APIs from the web platform is an important goal for the Moddable SDK implementations of deep copy and deep clone. Because Moddable did the extra work to deliver a well-known API, developers can apply their knowledge from the web to embedded systems, and potentially even share code between the two.

structuredClone is a true standard. It is defined by WHATWG as part of the HTML Living Standard. The Moddable SDK implements this specification precisely, varying only by omitting support for Web-only objects like DOMException and Blob that don't exist on embedded devices. structuredClone in the Moddable SDK handles circular references correctly and is able to clone preloaded objects stored in read-only flash memory. It also supports the transferables option which helps reduce memory use when cloning TypedArray instances in some situations.

deepEqual isn't quite standard. There are many valid ways to compare objects in JavaScript, and a consensus has not emerged. The Moddable SDK adopts the Node.js behavior for deep object comparison because it is a very reasonable approach that is widely used. As with structuredClone, deepEqual supports circular references and preloaded objects in read-only flash memory. Because there is no formal standard, the Moddable SDK implementation is based on review of the Node.js documentation and source code.

To ensure compatibility with the web, the Moddable SDK implementations of structuredClone and deepEqual are tested using the same test cases as the web versions. To allow the tests to be run on embedded devices, Moddable split the structuredClone tests and deepEqual tests across multiple files, but otherwise the tests are unchanged. Reading and running the tests is a good way to explore the capabilities of these functions. The tests can be run in the simulator and on some embedded devices (including ESP32) using testmc.

Easy to Use

Using deepEqual and structuredClone in your projects is easy. There are just three steps.

First, add the modules to your project's manifest. These functions aren't built-in so that they don't increase the code size of project's that don't use them.

"include": [
    "$(MODULES)/base/deepEqual/manifest.json",
    "$(MODULES)/base/structuredClone/manifest.json"
]

Next, import the deepEqual and structuredClone functions in your source code.

import deepEqual from "deepEqual";
import structuredClone from "structuredClone";

You're now ready to use the functions.

const a = {a: 1, b: Uint8Array.of(1, 2, 3), c: "0"};
const aCopy = structuredClone(a);
deepEqual(a, aCopy); // true
aCopy.c = 0;
deepEqual(a, aCopy); // true
deepEqual(a, aCopy, {strict: true});     // false

To learn more about these functions, check out the Moddable SDK documentation for deepEqual and structuredClone.

Conclusion

The deep equality and deep clone operations are now an officially supported part of the Moddable SDK. These operations are often useful in frameworks that pass objects as messages. For example, they are already being used in Node-RED MCU Edition to clone and compare messages.

The fast implementations and small code footprint make these new functions practical to use in embedded projects. By emulating behaviors from the web platform for these deep object operations, Moddable makes it easier for web developers to use more of their deep knowledge to build embedded software.

Note: Moddable thanks Jordan Harband for his helpful advice when we were trying to decide what behavior to implement for deep equality. Jordan's is-equal module was a great reference in understanding the behavior of Node.js for deep equality, and we use its tests to validate our implementation. Thank you, Jordan!