Table of Contents

Use CStructSharp from JavaScript

CStructSharp runs locally in Node.js and browsers through WebAssembly, often called WASM. Your JavaScript supplies a layout and bytes; the library returns values. Consumers do not need .NET installed.

Install from npm

npm install cstructsharp

On Node.js 22.14 or later, save this as example.mjs and run node example.mjs:

import { parse } from "cstructsharp";

try {
  const result = await parse(
    "struct header { uint16 kind; uint32 length; };",
    new Uint8Array([2, 0, 6, 0, 0, 0]),
    { root: "header" },
  );
  if (!result.success) throw new Error(result.error.message);
  console.log(result.data.kind); // 2
} catch (error) {
  console.error(error);
}

Node loads the runtime directly from the installed package, with no HTTP server or runtime downloads. Buffer is accepted as byte input. CommonJS callers can use await import("cstructsharp") inside an async function. Use the asynchronous import API rather than synchronous require(). Large or cancellable reads use workers automatically.

For a Vite browser app, add this to vite.config.js, then use the same API import in your app:

import { defineConfig } from "vite";
import { cstructsharp } from "cstructsharp/vite";

export default defineConfig({ plugins: [cstructsharp()] });

The plugin handles runtime assets in development and production. For other tools and nested deployment paths, see deployment. TypeScript declarations ship with the package.

For a first experiment with no setup, open the header lesson. Read the six bytes, change the first byte to 03, and read again. The kind changes from 2 to 3.

Alternative: run the standalone browser starter

You need a browser and Node.js to run the included local server. Download cstructsharp-wasm-v<VERSION>.zip from GitHub Releases. Use a release containing the starter directory; older bundles may contain only the library. Keep the complete extracted archive together:

cstructsharp-wasm/
  cstructsharp-wasm.js
  cstructsharp-api.js
  main.js
  bootstrap.js
  CStructSharpWeb.Wasm.runtimeconfig.json
  _framework/
  serve.mjs
  starter/
    index.html
    app.js

Open a terminal in cstructsharp-wasm and run:

node serve.mjs

Open http://127.0.0.1:8080/starter/. Wait for Ready, then select Read, write, and update. Press Ctrl+C in the terminal to stop the server. It listens only on your own computer.

The page reads 02 00 06 00 00 00, creates a header with kind 3, changes the kind to 4, and reads it again. The created bytes are 03 00 06 00 00 00; the updated bytes are 04 00 06 00 00 00. The parsed JSON contains a root object named header with kind and length fields.

Complete page and JavaScript

These are the actual files included in the bundle. You can copy them into a starter directory beside the runtime. The script import uses ../ to reach its parent directory.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Read your first binary header</title>
    <style>
      body {
        font: 1rem/1.6 system-ui;
        max-width: 48rem;
        margin: 3rem auto;
        padding: 0 1rem;
      }
      button {
        font: inherit;
        padding: 0.5rem 1rem;
      }
      pre {
        white-space: pre-wrap;
        overflow-wrap: anywhere;
        background: #eee;
        padding: 1rem;
      }
    </style>
  </head>
  <body>
    <h1>Read your first binary header</h1>
    <p>
      Six bytes describe a message kind and length. Read them, create new bytes, and change one
      field.
    </p>
    <p id="status" role="status">Loading WebAssembly…</p>
    <button id="run" disabled>Read, write, and update</button>
    <pre id="output" aria-live="polite"></pre>
    <script type="module" src="./app.js"></script>
  </body>
</html>
const status = document.querySelector("#status");
const output = document.querySelector("#output");
const button = document.querySelector("#run");
const definition = "struct header { uint16 kind; uint32 length; };";
const options = { root: "header", littleEndian: true, aligned: false, pointerSize: 8 };

// data holds the parsed value after a read, and a Uint8Array after a write or update.
function requireData(result) {
  if (!result.success) {
    const error = result.error;
    output.textContent = `Operation failed: ${error.code}\n${error.message}\nPath: ${error.path ?? "unknown"}\nOffset: ${error.offset ?? "unknown"}`;
    return null;
  }
  return result.data;
}

function hex(bytes) {
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(" ");
}

try {
  // A dynamic import lets us display a useful error even if the bundle is missing.
  const { loadCStructSharpWasm, parse, serialize, update } =
    await import("../cstructsharp-wasm.js");
  await loadCStructSharpWasm();
  status.textContent = "Ready";
  button.disabled = false;

  button.addEventListener("click", async () => {
    button.disabled = true;
    status.textContent = "Running…";
    output.textContent = "";
    try {
      const input = new Uint8Array([2, 0, 6, 0, 0, 0]);
      const parsed = requireData(await parse(definition, input, options));
      if (parsed === null) return;
      const values = parsed;
      const written = requireData(await serialize(definition, { kind: 3, length: 6 }, options));
      if (written === null) return;
      const bytes = written;
      const changed = requireData(await update(definition, bytes, "header.kind", 4, options));
      if (changed === null) return;
      const updatedBytes = changed;
      const reread = requireData(await parse(definition, updatedBytes, options));
      if (reread === null) return;
      output.textContent = [
        `Read: ${JSON.stringify(values)}`,
        `Created: ${hex(bytes)}`,
        `Updated: ${hex(updatedBytes)}`,
        `Read again: ${JSON.stringify(reread)}`,
      ].join("\n");
    } catch (error) {
      output.textContent = `JavaScript or runtime error: ${error.message}`;
    } finally {
      status.textContent = "Ready";
      button.disabled = false;
    }
  });
} catch (error) {
  status.textContent = "Could not load WebAssembly";
  output.textContent = `Serve the complete extracted bundle over HTTP. Check the browser Network tab for missing files.\n${error.message}`;
}

type="module" allows JavaScript imports. await waits for the runtime and the operation to finish. The result's success field tells you whether the operation worked. Parse data is the selected value; write and update data is a Uint8Array ready to use. The starter keeps operation failures separate from loading errors.

Try a change

In app.js, change the update replacement from 4 to 5, save, and reload the page. Predict the first output byte. Answer: the updated bytes start with 05; the final read reports kind 5. The length stays 6.

To see a failure, remove the final zero from the input array. The page reports read-failed. Restore it to fix the input. If the page never reaches Ready, follow Loading and deployment.

Continue with the JavaScript API and value guide and large files, buffers, and streams. The C# API has additional stream and memory operations; the browser API offers parse, serialize, update, and resolveAddress rather than every managed method.