JavaScriptでバーコード読み取り&パース処理を実装した話(GS1, DataMatrix対応)
業務システムにおいて、バーコードを読み込んでマスタデータと照合する機能をJavaScriptで実装したので、その内容を共有します。
特に GS1-128 のような複雑な構造を持つバーコードに対応するためのパース処理も含まれており、実務でも応用しやすい内容です。
今回は、GTIN(JANコード13桁)、使用期限、シリアル番号といった商品管理に不可欠な項目の抽出を目的としており、それらに対して最低限のバリデーションチェックも実装しています。
1. 対応バーコードの種類
本システムでは以下のバーコード種別に対応しました:
- GS1-128
- Data Matrix(GS1構造含む)
2. GS1バーコードの構造と仕様(補足)
GS1-128やData Matrixなどの2次元コードは、**AI(Application Identifier)**と呼ばれる識別子によってデータの意味が定義されています。
それぞれのAIには特定のフォーマットや長さがあり、バーコードから意味のある情報を取り出すためにはこの構造に沿った解析が必要です。
2.1 代表的なAIとその意味
| AI | 意味 | 桁数 | 例 |
|---|---|---|---|
01 |
GTIN(商品コード) | 固定14桁 | 04912345123456 |
17 |
使用期限(YYMMDD) | 固定6桁 |
250731 → 2025-07-31 |
21 |
シリアル番号 | 可変長(最大20桁) | ABC123XYZ |
240 |
追加情報(製品コードなど) | 可変長 | LOT1234 |
2.2 可変長データとFNC1
- 可変長のデータ(AI
21,240など)は、FNC1という区切り記号で次のAIと分離されます。 - 多くのバーコードリーダーでは、FNC1を特定のキー(例:F8)として送信してくるため、JS側でこれを考慮する必要があります。
このようにバーコードには明確な規格があり、それに従ったパース処理を行うことで、商品管理や在庫管理などの業務処理に活用できます。
3. 使用するHTML
バーコードリーダーの入力を受け取るため、input タグを配置するだけのシンプルな構成です。
(※pointer-events: none により、ユーザーによる直接入力は防ぎます)
<div>
<input id="rawcodeinput" type="url" style="top:0; left:0; width:80%; pointer-events:none;">
</div>
4. JavaScript実装
処理の流れ
- バーコードリーダーからのキー入力を監視
-
Enterキーで入力完了を検知 - GS1形式のバーコードをAI(アプリケーション識別子)単位でパース
- GTIN・使用期限・シリアル番号などを抽出し、バリデーション
function setupBarcodeInput() {
let keyBuffer = "";
let errorFlag = false;
// キーダウンイベントを処理
function handleKeyDown(e) {
if (errorFlag) {
playBeepSound(); // エラー中はビープ音で通知
return;
}
const pressedKey = e.key;
if (pressedKey === "Enter") {
const rawCode = $("#rawcodeinput").val();
console.log("取得コード:", rawCode);
const parsed = parseGS1CodeSequentially(rawCode);
console.log("Parsed Data:", parsed);
const gtin = parsed["01"];
const useByRaw = parsed["17"];
const serial = parsed["21"];
const additionalInfo = parsed["240"] || null;
// 必須項目のバリデーション
if (!gtin || gtin.length !== 14) {
return handleError("GTINの形式が正しくありません。");
}
if (!useByRaw || useByRaw.length !== 6) {
return handleError("使用期限の形式が正しくありません。");
}
if (!serial) {
return handleError("シリアル番号が存在しません。");
}
// 使用期限の形式変換(YYMMDD → YYYY-MM-DD)
const useBy = `20${useByRaw.slice(0, 2)}-${useByRaw.slice(2, 4)}-${useByRaw.slice(4, 6)}`;
console.log("GTIN:", gtin);
console.log("使用期限:", useBy);
console.log("シリアル:", serial);
console.log("追加情報 (240):", additionalInfo);
resetInput();
} else {
// 通常キー入力をバッファに追加
if (pressedKey === "F8") {
keyBuffer += "FNC1";
} else if (pressedKey !== "Shift") {
keyBuffer += pressedKey;
}
$("#rawcodeinput").val(keyBuffer);
}
}
// エラーハンドリング
function handleError(message) {
showInfoPopup('#barcode-container', message, () => {
resetInput();
errorFlag = false;
});
}
// 入力欄とバッファをリセット
function resetInput() {
$("#rawcodeinput").val('');
keyBuffer = "";
}
// キーイベント登録
addEventListener("keydown", handleKeyDown);
}
// GS1バーコードをAI単位で分解して抽出
function parseGS1CodeSequentially(input) {
input = input.replace(/FNC1/g, "#FNC1#");
const aiSpecs = {
"01": { length: 14 }, // GTIN
"17": { length: 6 }, // 使用期限 (YYMMDD)
"11": { length: 6 }, // 製造日など(任意)
"20": { length: 2 },
"21": { length: "variable" }, // シリアル番号
"240": { length: "variable" }, // 追加情報
"30": { length: "variable" },
};
const result = {};
let remaining = input;
while (remaining.length > 0) {
if (remaining.startsWith("#FNC1#")) {
remaining = remaining.slice(6);
continue;
}
let ai = remaining.slice(0, 2);
// AI「240」はプレフィックスが「240」でも「24+0」として判定
if (ai === "24" && remaining[2] === "0") {
ai = "240";
const endIndex = remaining.indexOf("#FNC1#") !== -1 ? remaining.indexOf("#FNC1#") : remaining.length;
result[ai] = remaining.slice(3, endIndex);
remaining = remaining.slice(endIndex);
continue;
}
const spec = aiSpecs[ai];
if (spec) {
if (spec.length === "variable") {
const endIndex = remaining.indexOf("#FNC1#") !== -1 ? remaining.indexOf("#FNC1#") : remaining.length;
result[ai] = remaining.slice(2, endIndex);
remaining = remaining.slice(endIndex);
} else {
result[ai] = remaining.slice(2, 2 + spec.length);
remaining = remaining.slice(2 + spec.length);
}
} else {
console.warn(`Unknown AI: ${ai}`);
remaining = remaining.slice(2);
}
}
return result;
}
5. 補足と工夫ポイント
-
F8キーをFNC1にマッピング
一部バーコードリーダーではFNC1(可変長区切り)をF8として送ってくる場合があります。それに対応するためF8 → FNC1の置換を行っています。 -
GS1コードの柔軟なパース処理
GS1-128はAIごとに長さが異なり、かつ可変長も含まれるため、柔軟な処理が必要です。今回はFNC1を使った区切り方式で実装しています。 -
使用期限の整形
AI「17」で取得できる使用期限(YYMMDD)をYYYY-MM-DDに変換する処理も含めました。 -
IMEの影響を完全に排除した入力処理
HTML側では<input type="url">+pointer-events: noneにすることで、ユーザーが直接フォーカス・入力できないようにし、IMEによる変換の影響を防いでいます。
さらにJavaScript側でもkeydownイベントを使い、キー入力を1文字ずつバッファに手動で格納する実装にしているため、IMEがオンの状態でも確実にキーコードを直接取得でき、安定して動作します。
6. 最終的な運用フロー
本記事ではJavaScriptによるバーコード読み取りとパース処理に焦点を当てていますが、
実際の運用では、パースして抽出した情報(GTIN・使用期限・シリアルなど)をJSON形式でサーバーに送信し、
マスターデータとの照合はサーバーサイドで行っています。
7. おわりに
バーコード処理は一見単純に見えて、実は複雑な規格や可変長フォーマットへの対応が求められます。
本記事が、現場での実装やライブラリに頼らず、自前で組む際の参考になれば幸いです!