tmatch Deep Object Matching
tmatch is a utility module designed to facilitate deep and flexible object matching, primarily used by the `t.match()` method in the `tap` test framework. Currently at version 5.0.0, it provides a comprehensive algorithm for comparing a target value against a pattern, supporting various data types including objects, arrays, regular expressions, dates, buffers, and constructor functions. Its matching logic goes beyond shallow equality, handling nested structures and specific type-based comparisons. For instance, it can match strings against regular expressions, check if an object is an `instanceof` a given constructor, or assert the absence of a property using `{propertyName: null}`. While its release cadence is tied to `tap`, it is generally stable. Key differentiators include its detailed, multi-step matching algorithm that accounts for many edge cases and its utility in robust assertion scenarios, offering a more nuanced comparison than standard deep equality checks.
Common errors
-
TypeError: tmatch is not a function
cause Attempting to import `tmatch` as a named export (`import { tmatch } from 'tmatch'`) or destructure it from a CommonJS `require` call.fixUse a default import for ESM (`import tmatch from 'tmatch';`) or assign the direct module export for CommonJS (`const tmatch = require('tmatch');`). -
My pattern `{foo: undefined}` isn't matching objects without `foo`!cause `tmatch` explicitly differentiates between an `undefined` value and a missing property. Using `undefined` in a pattern matches an existing property whose value is `undefined`.fixTo assert that a property `foo` is completely absent from the target object, set its value in the pattern to `null`: `{ foo: null }`. `tmatch` resolves missing keys to `undefined` during comparison, allowing `null` to effectively act as a 'does not exist' assertion.
Warnings
- gotcha Using `undefined` in a pattern property will match an existing property explicitly set to `undefined`, not a missing property. To assert that a property is *absent* from the target object, use `null` in the pattern instead.
- gotcha When a pattern value is a `Function` constructor (e.g., `String`, `Array`, `Buffer`), `tmatch` checks if the corresponding object value is an `instanceof` that constructor. This differs from value-based comparison.
- gotcha The algorithm's first step performs a loose equality check (`==`). This means `null` will match `undefined`, and some type coercions may occur before deeper comparison logic is applied. This can lead to unexpected matches if not accounted for.
- gotcha String patterns are matched as substrings (step 6: 'return true if the string occurs within the object'). If you need more complex string matching (e.g., starts with, ends with), a `RegExp` pattern is required.
Install
-
npm install tmatch -
yarn add tmatch -
pnpm add tmatch
Imports
- tmatch
import { tmatch } from 'tmatch';import tmatch from 'tmatch';
- tmatch
const { tmatch } = require('tmatch');const tmatch = require('tmatch');
Quickstart
const tmatch = require('tmatch');
// Simulate an HTTP response object
const mockResponse = {
statusCode: 200,
headers: {
'content-type': 'application/json',
server: 'express/4.17.1'
},
body: {
message: 'Hello, world!'
}
};
const expectedPattern = {
statusCode: 200,
headers: {
server: /express/, // Matches any server header containing 'express'
'content-type': 'application/json' // Exact string match
},
// 'body' property is not in pattern, so it's ignored during matching
};
if (tmatch(mockResponse, expectedPattern)) {
console.log('Response matches the expected pattern!');
} else {
console.error('Response DOES NOT match the expected pattern.');
console.error('Actual:', JSON.stringify(mockResponse, null, 2));
console.error('Pattern:', JSON.stringify(expectedPattern, null, 2));
}
// Example of asserting a property's absence using null
const negativePattern = {
headers: {
'x-powered-by': null // Ensures 'x-powered-by' property is absent
}
};
const responseWithXPoweredBy = {
statusCode: 200,
headers: {
'x-powered-by': 'Express',
server: 'express/4.17.1'
}
};
if (!tmatch(responseWithXPoweredBy, negativePattern)) {
console.log("Response does not have 'x-powered-by' header as expected (correct behavior for negative pattern).");
} else {
console.error("Response HAS 'x-powered-by' header, but pattern expected it absent.");
}