path-binder
path-binder
フラットデータにパスを書くだけ。
あとは path-binder が構造化する。
フラットデータをネストされた JSON に変換する TypeScript ライブラリ
こんなデータが、こうなる
| user.name | user.age | user.role |
|---|---|---|
| Taro | 25 | admin |
| Jiro | 30 | editor |
{
"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 のアプローチ — データモデルに直接マッピングするシンプルなラベル付け — はそのコストを完全に排除します。サポートチームと一緒に導入すれば、運用コストの削減効果は明らかです。
特徴
ゼロ依存関係
外部依存なし。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 になるまで
まず、変換の全体像を見てみましょう。
| user.name | user.age | user.tags[] |
|---|---|---|
| Taro | 25 | admin |
| Jiro | 30 | editor |
{
"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 に似た操作を、スプレッドシート上で実現します。
仕組み
- プライマリ行: 通常のパス(例:
user.id)を持つ行。ベースとなるデータです - 参照行:
$付きパス(例:user.$id)を持つ行。$を取り除いた名前(id)でプライマリ行の値とマッチングされ、データがマージされます
例: ユーザーマスタとメールデータの結合
2つのシートがあるとします。
Sheet1(プライマリ) — ユーザーの基本情報:
| user.id | user.name |
|---|---|
| 1 | Taro |
| 2 | Jiro |
Sheet2(参照) — $ 付きでメール情報を追加:
| user.$id | user.email |
|---|---|
| 1 | taro@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')
})
パラメータ
| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
input | InputData | はい | シートごとの { path, value } ペア配列 |
options.schema | SchemaObject | いいえ | 型キャスト・フィルタリング定義 |
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() | () => SchemaNode | String(value) |
asNumber() | () => SchemaNode | Number(value) |
asBoolean() | () => SchemaNode | Boolean(value) |
asDate() | () => SchemaNode | new 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.$id と user.id が同じ行にある |
nested_key | 配列パス内に $key がある | info[].$type |
invalid_key_value | キー値がプリミティブでない | $id の値がオブジェクト |
mixed_key_root | 同行の $key が異なるルートパスに属する | user.$id と order.$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 フォーマット例:
| UserId | UserName | LoginMethod |
|---|---|---|
| user.id | user.name | user.loginInfo[].type |
| 1 | Taro | ID/Password |
| 2 | Jiro | emailLink |
サンプル 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枚目のシートに
$参照キーを追加してみる