Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

path-binder

path-binder

フラットデータにパスを書くだけ。
あとは path-binder が構造化する。

フラットデータをネストされた JSON に変換する TypeScript ライブラリ

こんなデータが、こうなる

フラットデータ
user.nameuser.ageuser.role
Taro25admin
Jiro30editor
JSON 出力
{
  "user": [
    { "name": "Taro", "age": 25, "role": "admin" },
    { "name": "Jiro", "age": 30, "role": "editor" }
  ]
}

各列に JSON パスを書き、データを入れる。generate() を呼ぶ。それだけです。

3ステップではじめる

1. インストール

npm install path-binder

2. データを用意する

スプレッドシートの各列を { path, value } ペアに変換します。

import { generate } from 'path-binder'

const input = {
  Sheet1: [
    [{ path: 'user.name', value: 'Taro' }, { path: 'user.age', value: 25 }],
    [{ path: 'user.name', value: 'Jiro' }, { path: 'user.age', value: 30 }],
  ],
}

3. 変換する

const { result } = generate(input)
// → {
//   user: [
//     { name: 'Taro', age: 25 },
//     { name: 'Jiro', age: 30 }
//   ]
// }

なぜ path-binder?

toB SaaS 開発で、CSV やスプレッドシートなどのフラットデータを構造化 JSON に変換する必要に迫られたことはありませんか?

path-binder なら、各カラムに JSON パスのラベルを付けるだけ。 複雑な変換ロジックは不要です。一度パスを定義すれば、データのレイアウトがどう変わっても、マッピングは壊れません。

「Excel の関数で処理した方が楽では?」

一度きりの変換ならそうかもしれません。しかし、変化し続けるビジネス要件に合わせて複雑な数式チェーンをメンテナンスし続けるコストは膨大です。path-binder のアプローチ — データモデルに直接マッピングするシンプルなラベル付け — はそのコストを完全に排除します。サポートチームと一緒に導入すれば、運用コストの削減効果は明らかです。

特徴

0

ゼロ依存関係

外部依存なし。node_modules を汚さず、軽量で高速に動作します。

{}

型安全

スキーマ定義に基づく厳密な型推論。TypeScript の恩恵を最大限に活かせます。

スキーマ対応

キャスト・フィルタを宣言的に定義。不要な列は自動除外されます。

📊

マルチシート

複数シートのデータを参照キー($)で自動結合。リレーショナルな構造も1回の呼び出しで。

さらに高度な使い方

スキーマによる型変換と、$ 参照キーによるマルチシート結合を組み合わせた例です。

import { generate, defineSchema, asNumber, asString, arrayOf } from 'path-binder'

const input = {
  sheetA: [
    [{ path: 'user.id', value: 1 }, { path: 'user.name', value: 'Taro' }],
    [{ path: 'user.id', value: 2 }, { path: 'user.name', value: 'Jiro' }],
  ],
  sheetB: [
    [{ path: 'user.$id', value: 1 }, { path: 'user.info[].type', value: 'google' }],
  ],
}

const schema = defineSchema({
  user: {
    id: asNumber(),
    name: asString(),
    info: arrayOf({ type: asString() }),
  },
})

const { result } = generate(input, { schema })
// → {
//   user: [
//     { id: 1, name: 'Taro', info: [{ type: 'google' }] },
//     { id: 2, name: 'Jiro' },
//   ]
// }

次のステップ: パス構文でネストや配列の表現方法を学ぶ → スキーマで型キャストとフィルタリングを設定する → プレイグラウンドで試す

パス構文

path-binder は { path, value } ペアの path を解釈して、フラットなデータをネストされた JSON 構造に変換します。このページでは、パスの書き方とその変換ルールを解説します。

全体像: パスが JSON になるまで

まず、変換の全体像を見てみましょう。

スプレッドシート(2行目がパス)
user.nameuser.ageuser.tags[]
Taro25admin
Jiro30editor
生成される JSON
{
  "user": [
    { "name": "Taro", "age": 25, "tags": ["admin"] },
    { "name": "Jiro", "age": 30, "tags": ["editor"] }
  ]
}

パスのドット (.) がオブジェクトの階層に、[] が配列に対応します。以下で各構文を詳しく見ていきます。


プロパティ(ドット記法)

いつ使う: オブジェクトのネスト構造を表現したいとき

ドット (.) で区切ることで、ネストされたプロパティを定義します。

パス生成される JSON
name"Taro"{ "name": "Taro" }
user.name"Taro"{ "user": { "name": "Taro" } }
user.address.city"Tokyo"{ "user": { "address": { "city": "Tokyo" } } }
import { generate } from 'path-binder'

const input = {
  Sheet1: [
    [{ path: 'user.name', value: 'Taro' }, { path: 'user.address.city', value: 'Tokyo' }],
  ],
}

const { result } = generate(input)
// → { user: [{ name: 'Taro', address: { city: 'Tokyo' } }] }

ドットの深さに制限はありません。a.b.c.d.e のように自由にネストできます。


配列追加 []

いつ使う: 複数行のデータを配列として集めたいとき

パスの末尾に [] を付けると、各行の値が配列の要素として追加されます。

パス行1の値行2の値生成される JSON
tags[]"admin""editor"{ "tags": ["admin", "editor"] }
user.skills[]"TypeScript""React"{ "user": [{ "skills": ["TypeScript", "React"] }] }
const input = {
  Sheet1: [
    [{ path: 'user.name', value: 'Taro' }, { path: 'user.tags[]', value: 'admin' }],
    [{ path: 'user.name', value: 'Taro' }, { path: 'user.tags[]', value: 'editor' }],
  ],
}

const { result } = generate(input)
// → { user: [{ name: 'Taro', tags: ['admin', 'editor'] }] }

インデックスアクセス [n]

いつ使う: 配列の特定の位置に値をセットしたいとき

[n] で配列の n 番目に直接値を設定します。指定されていない位置は undefined になります。

パス生成される JSON
items[0]"first"{ "items": ["first"] }
items[2]"third"{ "items": [undefined, undefined, "third"] }
const input = {
  Sheet1: [
    [{ path: 'scores[0]', value: 90 }, { path: 'scores[1]', value: 85 }],
  ],
}

const { result } = generate(input)
// → { scores: [[90, 85]] }

参照キー $ — 別シートのデータを結合する

いつ使う: 複数のシートに分かれたデータを、共通のキーで1つのオブジェクトに統合したいとき

これは path-binder の最も強力な機能です。リレーショナルデータベースの JOIN に似た操作を、スプレッドシート上で実現します。

仕組み

  1. プライマリ行: 通常のパス(例: user.id)を持つ行。ベースとなるデータです
  2. 参照行: $ 付きパス(例: user.$id)を持つ行。$ を取り除いた名前(id)でプライマリ行の値とマッチングされ、データがマージされます

例: ユーザーマスタとメールデータの結合

2つのシートがあるとします。

Sheet1(プライマリ) — ユーザーの基本情報:

user.iduser.name
1Taro
2Jiro

Sheet2(参照)$ 付きでメール情報を追加:

user.$iduser.email
1taro@example.com

user.$id$ を取り除くと user.id になります。Sheet2 の $id = 1 は、Sheet1 の id = 1(Taro)とマッチし、メール情報がマージされます。

import { generate } from 'path-binder'

const input = {
  Sheet1: [
    [{ path: 'user.id', value: 1 }, { path: 'user.name', value: 'Taro' }],
    [{ path: 'user.id', value: 2 }, { path: 'user.name', value: 'Jiro' }],
  ],
  Sheet2: [
    [{ path: 'user.$id', value: 1 }, { path: 'user.email', value: 'taro@example.com' }],
  ],
}

const { result } = generate(input)
// → {
//   user: [
//     { id: 1, name: 'Taro', email: 'taro@example.com' },
//     { id: 2, name: 'Jiro' }
//   ]
// }

参照キーの制約

制約説明
プライマリ行が必須すべての行が参照行($ 付き)だとスキップされます
キー値はプリミティブ$key の値は文字列・数値・真偽値のみ(オブジェクトは不可)
同一ルートパス同じ行の $key は同じルートパスに属する必要があります
ネスト不可配列パス内での $key(例: info[].$type)は使用できません

参照キー関連のエラーが発生した場合は、スキップ処理ページで詳しい原因と対処法を確認できます。


エスケープ $$

いつ使う: パス内にリテラルの $ 文字を含めたいとき

$ は通常、参照キーの特殊文字として解釈されます。リテラルの $ を使いたい場合は $$ でエスケープします。

パス生成される JSON
config.$$ref{ "config": { "$ref": "..." } }
data.$$type{ "data": { "$type": "..." } }
const input = {
  Sheet1: [
    [{ path: 'schema.$$ref', value: '#/definitions/User' }],
  ],
}

const { result } = generate(input)
// → { schema: [{ $ref: '#/definitions/User' }] }

組み合わせパターン

実務でよく使うパスの組み合わせパターンです。

ネスト + 配列: ユーザーの住所リスト

// パス: user.addresses[].city, user.addresses[].zip
// → { user: [{ addresses: [{ city: 'Tokyo', zip: '100-0001' }, ...] }] }

配列内オブジェクト: 注文明細

// パス: order.items[].name, order.items[].price, order.items[].qty
// → { order: [{ items: [{ name: '...', price: 100, qty: 2 }, ...] }] }

参照キー + 配列: マスタデータの結合

// Sheet1: product.id, product.name
// Sheet2: product.$id, product.reviews[].comment
// → プライマリの商品データに、参照行のレビュー配列がマージされる

次のステップ: スキーマを使って型変換とフィルタリングを設定する

スキーマ

なぜスキーマが必要か

generate() はスキーマなしでも動作します。では、なぜスキーマを定義するのでしょうか?

1. 型変換 — 入力値を正しい型に

入力データは文字列や数値が混在していることがあります。スキーマで型を指定すれば、出力 JSON の型を保証できます。

// スキーマなし: age が文字列 "25" のまま
{ name: 'Taro', age: '25' }

// asNumber() を使用: age が数値 25 に変換
{ name: 'Taro', age: 25 }

2. フィルタリング — 不要な列を自動除外

スキーマは許可リストとして機能します。定義されたパスのみが出力に含まれ、それ以外は自動的に除外されます。入力データの余分な列が、API のレスポンスを汚染する心配がありません。

// 入力データに「備考」「担当者」列があっても、スキーマに定義がなければ除外される
const schema = defineSchema({
  customer: {
    name: asString(),
    email: asString(),
    // ← 「備考」「担当者」はここに定義がないため出力から除外
  },
})

キャスト関数

各フィールドの型変換を定義する組み込み関数です。

関数変換内容入力例出力例
asString()String(value)123"123"
asNumber()Number(value)"42"42
asBoolean()Boolean(value)"true"true
asDate()new Date(value)"2024-01-15"Date オブジェクト
asCustom(fn)カスタム関数任意任意
import { defineSchema, asString, asNumber, asBoolean, asDate, asCustom } from 'path-binder'

const schema = defineSchema({
  user: {
    name: asString(),
    age: asNumber(),
    active: asBoolean(),
    joinedAt: asDate(),
    score: asCustom((v) => Math.round(Number(v))),
  },
})

配列スキーマ

配列の各要素に対するスキーマを arrayOf() で定義します。

import { defineSchema, asString, asNumber, arrayOf } from 'path-binder'

const schema = defineSchema({
  user: {
    name: asString(),
    // プリミティブ値の配列
    tags: arrayOf(asString()),
    // オブジェクト配列
    contacts: arrayOf({
      type: asString(),
      value: asString(),
    }),
  },
})

ルーズスキーマ(asAny)

いつ使う: プロトタイプ段階で全列を通したい場合や、一部だけ型変換したい場合

import { defineSchema, asAny, asNumber } from 'path-binder'

// 全パスをキャストなしで通す
const looseSchema = defineSchema({
  user: asAny(),
})

// 全パスを通すが、age だけは数値にキャスト
const partialSchema = defineSchema({
  user: asAny({
    age: asNumber(),
  }),
})

asAny() はフィルタリングを無効にします。スキーマに定義されていないパスも出力に含まれます。


カスタムキャスト

asCustom() で任意の変換ロジックを定義します。

カンマ区切りタグの分割

const schema = defineSchema({
  product: {
    tags: asCustom((v) => String(v).split(',').map((t) => t.trim())),
    // "食品,冷凍,セール" → ["食品", "冷凍", "セール"]
  },
})

例外スロー時の動作

キャスト関数内で例外がスローされた場合、そのエントリはスキップされます。

const schema = defineSchema({
  config: {
    priority: asCustom((v) => {
      const n = Number(v)
      if (Number.isNaN(n)) {
        throw new Error('Invalid number')
      }
      return n
    }),
  },
})

スキーマあり vs なしの比較

項目スキーマなしスキーマあり
型変換なし(元の値のまま)キャスト関数で制御
不要列の除外されない(全パスが出力)許可リストとして機能
例外時のスキップなしキャスト関数が例外をスローした場合、エントリをスキップ
開発速度速い(定義不要)やや遅い(定義が必要)
本番利用非推奨推奨

次のステップ: スキップ処理でエラーハンドリングを設定する

API リファレンス

generate(input, options?)

フラットなスプレッドシートデータをネストされた JSON に変換するメイン関数です。

import { generate } from 'path-binder'

const { result, skipped } = generate(input, {
  schema,              // optional: SchemaObject
  skipScope: 'cell',   // optional: 'cell' | 'row'(デフォルト: 'cell')
})

パラメータ

パラメータ必須説明
inputInputDataはいシートごとの { path, value } ペア配列
options.schemaSchemaObjectいいえ型キャスト・フィルタリング定義
options.skipScope'cell' | 'row'いいえスキップの粒度(デフォルト: 'cell'

戻り値: GenerateResult

type GenerateResult = {
  readonly result: Record<string, unknown>
  readonly skipped: readonly ParseSkipped[]
}
  • result: トップレベルプロパティをキーとし、値は常に配列です。スプレッドシートの各行が1つのエンティティに対応するためです。
// 入力: user.name = "Taro", user.name = "Jiro" の2行
// 結果: { user: [{ name: 'Taro' }, { name: 'Jiro' }] }
//        ↑ user は常に配列
  • skipped: 処理できなかったエントリの配列。詳細はスキップ処理を参照。

InputData 形式

3層構造: シート → 行 → セル

type InputData = {
  readonly [sheetName: string]: readonly (readonly PathValuePair[])[]
}
//                               ↑ 行の配列        ↑ セル(path + value ペア)の配列

type PathValuePair = {
  readonly path: string
  readonly value: unknown
}

視覚的に表すと:

InputData = {
  "Sheet1": [          // ← シート名
    [                  // ← 1行目
      { path, value }, // ← セル1
      { path, value }, // ← セル2
    ],
    [                  // ← 2行目
      { path, value },
      { path, value },
    ],
  ],
  "Sheet2": [ ... ],   // ← 別シート
}

defineSchema(definition)

スキーマオブジェクトを定義します。

import { defineSchema, asString, asNumber } from 'path-binder'

const schema = defineSchema({
  user: {
    name: asString(),
    age: asNumber(),
  },
})

キャスト関数

関数シグネチャ変換
asString()() => SchemaNodeString(value)
asNumber()() => SchemaNodeNumber(value)
asBoolean()() => SchemaNodeBoolean(value)
asDate()() => SchemaNodenew Date(value)
asCustom(fn)(fn: CastFn) => SchemaNodeカスタム関数で変換
asAny()(partial?: object) => SchemaNodeキャストなし(全パス通過)
arrayOf(schema)(schema: SchemaNode | object) => ArraySchema配列要素のスキーマ定義
type CastFn = (value: unknown) => unknown

型エクスポート

import type {
  InputData,         // 入力データの型
  PathValuePair,     // { path: string, value: unknown }
  GenerateOptions,   // generate() のオプション
  GenerateResult,    // generate() の戻り値
  ParseSkipped,      // スキップされたエントリ
  ParseSkipReason,   // スキップ理由コード
  SchemaObject,      // defineSchema() の引数型
  SchemaNode,        // スキーマの各ノード
  CastFn,            // キャスト関数の型
  ArraySchema,       // arrayOf() の戻り値型
  AnySchema,         // asAny() の戻り値型
} from 'path-binder'

スキップ処理

path-binder は不正なデータに遭遇してもエラーをスローしません。代わりに、処理できなかったエントリを skipped 配列に収集して返します。これにより、1行の不正データがバッチ全体を停止させることを防ぎます。

スキップされたエントリの確認

import { generate } from 'path-binder'

const { result, skipped } = generate(input, { schema })

skipped.forEach((entry) => {
  console.log(entry.name)    // シート名
  console.log(entry.index)   // 行インデックス
  console.log(entry.path)    // 問題のあったパス文字列
  console.log(entry.value)   // そのセルの値(文字列化)
  console.log(entry.reason)  // スキップ理由コード
})

skipScope オプション

スキップの粒度を制御します。

skipScope動作推奨シーン
'cell'(デフォルト)無効なセルだけスキップし、同じ行の他のセルは処理するユーザー入力データ(多少の欠損は許容)
'row'行内のいずれかのセルが無効なら、行全体をスキップ厳密なデータ変換(不完全なエンティティを防止)
// セルレベル: 無効なセルのみスキップ
const cellResult = generate(input, { skipScope: 'cell' })

// 行レベル: 1つでも無効なセルがあれば行全体をスキップ
const rowResult = generate(input, { skipScope: 'row' })

よくあるスキップと対処法

empty — パスセグメントが空

発生する入力: "user..name"(ドットが2つ)、"user."(末尾ドット)

対処: パス行を確認し、余分なドットを削除してください。

reference_not_found — 参照先が見つからない

発生する入力: Sheet2 で user.$id = 3 と書いたが、Sheet1 に id = 3 のユーザーがいない

対処: 参照先のシートにマッチするプライマリデータが存在するか確認してください。

cast — キャスト関数が例外をスロー

発生する入力: asNumber() を指定したフィールドに "abc" が入力された

対処: 入力データの内容を確認するか、asCustom() でエラーハンドリングを追加してください。


スキップ理由 一覧

パス構文エラー

コード説明発生する入力例
emptyパスセグメントが空"user..name"
bracket閉じ括弧 ] がない"items[0"
index配列インデックスが数値でない"items[abc]"
unnamed[] の前にプロパティ名がない"[]"
escape$$ の後に名前がない"config.$$"
key$ の後に名前がない"user.$"

参照キー関連エラー

コード説明発生する入力例
reference_not_found一致するプライマリ行がない$id=3 だがプライマリに id=3 がない
no_primary_data全行が参照行でプライマリがないすべてのパスに $ が付いている
conflicting_key_prop同行に $key と非 $key の同名プロパティuser.$iduser.id が同じ行にある
nested_key配列パス内に $key があるinfo[].$type
invalid_key_valueキー値がプリミティブでない$id の値がオブジェクト
mixed_key_root同行の $key が異なるルートパスに属するuser.$idorder.$id が同行

データ競合・キャストエラー

コード説明発生する入力例
property_conflict参照データがプライマリと競合両方が同じプロパティに異なる値を持つ
castキャスト関数が例外をスローasNumber()"abc"

スキップログの活用パターン

本番環境でスキップを監視する例です。

const { result, skipped } = generate(input, { schema })

// スキップが発生した場合のログ出力
if (skipped.length > 0) {
  console.warn(`${skipped.length} 件のエントリがスキップされました`)
  skipped.forEach((s) => {
    console.warn(`  シート: ${s.name}, 行: ${s.index + 1}, パス: ${s.path}, 理由: ${s.reason}`)
  })
}

// ユーザーへのフィードバック用レポート生成
const errorReport = skipped.map((s) =>
  `行 ${s.index + 1}: "${s.path}" — ${s.reason}`
)

次のステップ: API リファレンスで全ての型とオプションを確認する

プレイグラウンド

Excel ファイル (.xlsx) をドロップして、path-binder でネストされた JSON に変換します。各シートの2行目が JSON パス、3行目以降がデータです。

Excel フォーマット例:

UserIdUserNameLoginMethod
user.iduser.nameuser.loginInfo[].type
1TaroID/Password
2JiroemailLink

サンプル Excel をダウンロード(単一シート) | サンプル Excel をダウンロード(複数シート・$ 参照)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>path-binder プレイグラウンド</title>
  <style>
    body { font-family: sans-serif; margin: 20px; }
    #dropzone {
      border: 2px dashed #1a73e8;
      border-radius: 8px;
      padding: 40px;
      text-align: center;
      color: #5f6368;
      cursor: pointer;
      transition: background-color 0.2s;
    }
    #dropzone.dragover { background-color: rgba(26,115,232,0.08); }
    #output { white-space: pre-wrap; font-family: monospace; margin-top: 16px; }
  </style>
</head>
<body>
  <div id="dropzone">
    .xlsx ファイルをここにドロップ、またはクリックして選択
    <!-- Not using D&D alone: cross-origin iframe nesting blocks drop events. -->
    <input type="file" id="file-input" accept=".xlsx" style="display:none">
  </div>
  <h3>結果</h3>
  <pre id="output">ファイルを待っています...</pre>
  <script type="module" src="./index.ts"></script>
</body>
</html>
import ExcelJS from 'exceljs'
import { generate, defineSchema, asNumber, asString, arrayOf } from 'path-binder'
import type { InputData, PathValuePair, GenerateResult } from 'path-binder'

// =====================================================================
// ✏️ この関数を編集して、スキーマやオプションを試してみよう!
//
// 例:
//   - 型キャストを追加:    id: asNumber()
//   - skipScope を指定:    generate(inputData, { schema, skipScope: 'row' })
//   - スキーマを外す:      return generate(inputData)
// =====================================================================
function example(inputData: InputData): GenerateResult {
  const schema = defineSchema({
    user: {
      id: asNumber(),
      name: asString(),
      loginInfo: arrayOf({ type: asString() }),
    },
  })

  return generate(inputData, { schema })
}

// Row 1: header labels (ignored by path-binder)
// Row 2: JSON paths
// Row 3+: data values
const PATH_ROW_INDEX = 2

/**
 * Reads an Excel workbook buffer and converts it to path-binder InputData.
 *
 * Uses sheet name as the key and row 2 as paths.
 * Not reading paths from row 1: row 1 is a human-readable header,
 * keeping paths on row 2 makes it easy to swap path definitions
 * without touching the header.
 */
async function excelToInputData(buffer: ArrayBuffer): Promise<InputData> {
  const workbook = new ExcelJS.Workbook()
  await workbook.xlsx.load(buffer)

  const inputData: Record<string, PathValuePair[][]> = {}

  workbook.eachSheet((sheet) => {
    const pathRow = sheet.getRow(PATH_ROW_INDEX)
    const paths: string[] = []
    pathRow.eachCell((cell, colNumber) => {
      paths[colNumber] = String(cell.value ?? '')
    })

    const rows: PathValuePair[][] = []
    sheet.eachRow((row, rowNumber) => {
      if (rowNumber <= PATH_ROW_INDEX) {
        return
      }

      const pairs: PathValuePair[] = []
      row.eachCell((cell, colNumber) => {
        const path = paths[colNumber]
        if (!path) {
          return
        }
        pairs.push({ path, value: cell.value })
      })

      if (pairs.length > 0) {
        rows.push(pairs)
      }
    })

    inputData[sheet.name] = rows
  })

  return inputData
}

async function processFile(file: File, dropzone: HTMLElement, output: HTMLElement): Promise<void> {
  dropzone.textContent = `読み込み完了: ${file.name}`

  try {
    const buffer = await file.arrayBuffer()
    const inputData = await excelToInputData(buffer)
    const { result, skipped } = example(inputData)

    output.textContent = JSON.stringify(result, null, 2)
    if (skipped.length > 0) {
      output.textContent += '\n\n--- スキップ ---\n' + JSON.stringify(skipped, null, 2)
    }
  } catch (err) {
    output.textContent = 'エラー: ' + String(err)
  }
}

function setupDropzone(): void {
  const dropzone = document.getElementById('dropzone')
  const output = document.getElementById('output')
  const fileInput = document.getElementById('file-input') as HTMLInputElement | null
  if (!dropzone || !output || !fileInput) {
    return
  }

  // Click to open file picker.
  // Not relying on D&D alone: cross-origin iframe nesting blocks drop events.
  dropzone.addEventListener('click', () => {
    fileInput.click()
  })

  fileInput.addEventListener('change', async () => {
    const file = fileInput.files?.[0]
    if (!file) {
      return
    }
    await processFile(file, dropzone, output)
  })

  dropzone.addEventListener('dragover', (e) => {
    e.preventDefault()
    dropzone.classList.add('dragover')
  })

  dropzone.addEventListener('dragleave', () => {
    dropzone.classList.remove('dragover')
  })

  dropzone.addEventListener('drop', async (e) => {
    e.preventDefault()
    dropzone.classList.remove('dragover')

    const file = e.dataTransfer?.files[0]
    if (!file) {
      return
    }
    await processFile(file, dropzone, output)
  })
}

setupDropzone()

Excel フォーマットのルール

  • 1行目 — 人間が読むヘッダー(無視されます)
  • 2行目 — path-binder 用の JSON パス(例: user.id, user.name, user.loginInfo[].type
  • 3行目以降 — データ値
  • シート名 — InputData のトップレベルキーとして使用

試してみよう

  • example() 関数を編集してスキーマを変更してみる
  • Excel の2行目のパスを変更してみる
  • 2枚目のシートに $ 参照キーを追加してみる