Very tiny function that checks if an object/array/value is shaped like another, with TypeScript type refining.

Overview

@suchipi/has-shape

Very tiny (~200B before minification/compression) function that checks if an object/array/value is shaped like another, with TypeScript type refining.

import { hasShape } from "@suchipi/has-shape";

const someObj = {
  sheep: true,
  cows: ["betsy", "donna"],
  tags: {
    something: {
      active: true,
      description: "Something",
    },
    anotherThing: {
      active: false,
      description: "Another thing",
    },
  },
};

hasShape(someObj, { tags: { something: { active: true } } }); // true
hasShape(someObj, { tags: { something: { active: false } } }); // false
hasShape(someObj, { tags: { something: { blahbla: true } } }); // false; no blahbla property present
hasShape(someObj, { cows: { 0: "betsy" } }); // true; property keys are used for both objects and arrays
hasShape(someObj, { cows: { 0: "donna" } }); // false; donna is at property '1'
hasShape(someObj, { cows: { length: 2 } }); // true; reading property "length" from Array returns matching value
hasShape(someObj, { cows: { length: 4 } }); // false
hasShape(someObj, { tags: {} }); // true; validates it's a non-null object, but nothing else

Logic

The function behaves as follows:

  • Consider a function hasShape which receives input and shape, defined as follows:
  • If input is a primitive value, return input === shape.
  • Otherwise, return true if hasShape(input[property], shape[property]) is true for every own property in shape.

TypeScript

hasShape will tell TypeScript that the object has the specified shape if you put it in an if, like so:

import { hasShape } from "@suchipi/has-shape";

function runJob(
  options?:
    | { kind: "User"; name: string; id: number }
    | { kind: "Ticket"; ticket: string }
) {
  // `options` could be one of a few things
  if (hasShape(options, { kind: "User" })) {
    // Within this block, TypeScript knows `options` is `{ kind: "User", name: string, id: number }`,
    // because that's the only value in the union that could have returned true from hasShape.
  } else {
    // Within this block, TypeScript knows `options` could be `undefined` or `{ kind: "Ticket", ticket: string }`
    // here, and knows it could not be `{ kind: "User", name: string, id: number }`, because if it was,
    // the other block would have been taken instead of this one.
  }
}

This is most useful for deep structures with many different potential but distinguishable shapes, like ASTs:

import { traverse } from "@babel/traverse";
import { parse } from "@babel/parser";
import { hasShape } from "@suchipi/has-shape";

const someCode = "...";
const ast = parse(someCode);
traverse(ast, {
  CallExpression(path) {
    if (
      hasShape(path.node, {
        callee: {
          type: "Identifier",
          name: "require",
        },
        arguments: {
          length: 1,
          0: {
            type: "StringLiteral",
            value: "hello",
          },
        },
      })
    ) {
      // We now know it's a path pointing to a node with the shape `require("hello")`,
      // and TypeScript will let us treat it as such:
      console.log(path.node.callee.arguments[0].value); // No type or runtime errors!
    }
  },
});

If your shape is gonna be stored in a variable instead of being put directly in the function call, put as const at the end of the variable declaration, otherwise TypeScript won't "know" everything (ie. type refinement might not work well enough):

const targetShape = { blah: { wow: { isCool: true } } } as const;

hasShape(something, targetShape);

Additionally, if you're using an older version of TypeScript, you might need to add as const even if you're not storing it in a variable:

// If TypeScript doesn't seem to behave correctly when you do this:
hasShape(something, { blah: { wow: { isCool: true } } });
// Then do this instead:
hasShape(something, { blah: { wow: { isCool: true } } } as const);

License

MIT

You might also like...

Fix for Object.keys, which normally just returns an array of strings, which is not good when you care about strong typing

Fix for Object.keys, which normally just returns an array of strings, which is not good when you care about strong typing

Welcome to ts-object-keys 👋 Fix for Object.keys, which normally just returns an array of strings, which is not good when you care about strong typing

Jul 4, 2022

🐬 A simplified implementation of TypeScript's type system written in TypeScript's type system

🐬 A simplified implementation of TypeScript's type system written in TypeScript's type system

🐬 HypeScript Introduction This is a simplified implementation of TypeScript's type system that's written in TypeScript's type annotations. This means

Dec 20, 2022

A simple in-memory key-value cache for function execution, allowing both sync and async operations using the same methods

A simple in-memory key-value cache for function execution, allowing both sync and async operations using the same methods. It provides an invalidation mechanism based both on exact string and regex.

Dec 15, 2022

Resolve parallel promises in key-value pairs whilst maintaining type information

async-kv Resolves promises in key-value pairs maintaining type information. Prerequisites NodeJS 12 or later Installation npm i async-kv yarn add asyn

Feb 17, 2022

🚀 A Node.js server that automaticaly finds and checks proxies for you.

🚀 A Node.js server that automaticaly finds and checks proxies for you.

Proxi A Node.js app that automaticaly finds and checks proxies for you and shows them on a dashboard. Install & Setup ## Download git clone https://gi

Jul 7, 2022

An implementation of gRPC health checks, for node.js-based apps that uses @grpc/grpc-js as a base.

gRPC Health Check An implementation of gRPC health checks, for node.js-based apps that uses @grpc/grpc-js as a base. Installation yarn add git+https:/

Aug 31, 2022

GitHub Action that checks code and docs for offensive / exclusive terms and provides warnings.

GitHub Action that checks code and docs for offensive / exclusive terms and provides warnings.

Inclusiveness Analyzer Make your code inclusive! The Inclusiveness Analyzer is a GitHub action that checks your repository for offensive / exclusive t

Dec 1, 2022

A to-do list Web application that lets the user add, remove and reorder to do lists and checks a task when completed with a button to delete all completed task

TO DO LISTS A Web application that lets the user add, remove and reorder to do lists Built With Html,JS,CSS Webpack and other dependencies Git, Github

Nov 1, 2022

Tiny JavaScript library (1kB) by CurrencyRate.today, providing simple way and advanced number, money and currency formatting and removes all formatting/cruft and returns the raw float value.

Zero dependency tiny JavaScript library (1kB bytes) by CurrencyRate.today, providing simple way and advanced number, money and currency formatting and removes all formatting/cruft and returns the raw float value.

Nov 8, 2022
Owner
Lily Skye
Former Prettier Core Maintainer, Former Babel team member. Interested in JavaScript, TypeScript, Rust, Unity, VR, Dev Tooling, Dev Experience, React, and more.
Lily Skye
Combine type and value imports using Typescript 4.5 type modifier syntax

type-import-codemod Combines your type and value imports together into a single statement, using Typescript 4.5's type modifier syntax. Before: import

Ian VanSchooten 4 Sep 29, 2022
Awesome book with ES6, this project is build using HTML,CSS, JavaScript ES6 the project allows you to add books and save them with the author , for another time checks

Project Name Awsome books Description the project. adding books daynamiclly Built With Major languages Frameworks Technologies used Live Demo (if avai

Alzubair Alqaraghuli 5 Jul 25, 2022
Tries to execute sync/async function, returns a specified default value if the function throws

good-try Tries to execute sync/async function, returns a specified default value if the function throws. Why Why not nice-try with it's 70+ million do

Antonio Stoilkov 14 Dec 8, 2022
Type predicate functions for checking if a value is of a specific type or asserting that it is.

As-Is Description As-Is contains two modules. Is - Type predicates for checking values are of certain types. As - Asserting values are of a certain ty

Declan Fitzpatrick 8 Feb 10, 2022
we learn the whole concept of JS including Basics like Object, Functions, Array etc. And Advance JS - Understanding DOMs, JQuery, Ajax, Prototypes etc.

JavaScript-for-Complete-Web Development. we learn the whole concept of JS including Basics like Object, Functions, Array etc. And Advance JS - Underst

prasam jain 2 Jul 22, 2022
This is a (pretty broken, but mostly functional) organic-shaped jigsaw generator with custom border support

OrganicPuzzleJs This is a (pretty broken, but mostly functional) organic-shaped jigsaw generator with custom border support. It relies on two linbrari

null 6 Dec 10, 2022
❤️ A heart-shaped toggle switch component for React.

heart-switch A heart-shaped toggle switch component for React. Inspired by Tore Bernhoft's I heart toggle Dribbble shot. ?? Table of Contents ?? Getti

Anatoliy Gatt 413 Dec 15, 2022
100% type-safe query builder for node-postgres :: Generated types, call any function, tree-shakable, implicit type casts, and more

⚠️ This library is currently in alpha. Contributors wanted! tusken Postgres client from a galaxy far, far away. your database is the source-of-truth f

alloc 54 Dec 29, 2022
An exercise in building a very minimal (and very stupid) in-memory SQL-like database for educational purposes.

Stupid Database This is an exercise in building a very minimal (and very stupid) in-memory SQL-like database for educational purposes. None of this co

Fabio Akita 196 Dec 20, 2022
🧩 TypeScript utility type in order to ensure to return only properties (not methods) containing values in primitive types such as number or boolean (not Value Objects)

?? TypeScript Primitives type TypeScript utility type in order to ensure to return only properties (not methods) containing values in primitive types

CodelyTV 82 Dec 7, 2022