前言

Mapping editor 要讓商家選「Shopify 訂單編號寫到 Ragic 哪個欄位」,首先得知道 Ragic 表單有哪些 Field ID、哪些欄位可寫、哪些是自動編號、哪些是 Link/Load 或子表格。

只靠手動輸入 Field ID 很容易打錯,也無法在 UI 顯示足夠脈絡。這個案例因此建立一份可貼入 App 的 Ragic API 文件,再由 parser 轉成表單與欄位 catalog,最後交給 Mapping editor 使用。

先說清楚邊界:這裡的「Ragic API 文件」是專案自訂的繁體中文 Markdown 模板,不是 Ragic 官方承諾可供任意程式解析的 schema endpoint,也不是把任何官方說明頁貼進來都能工作。

上一篇說明了 為什麼欄位 Mapping 最終改回人工確認。這篇往前追,看看人工 editor 的選項怎麼可靠產生。

原本的做法與問題症狀

最簡單的設定表單可以只有兩個文字欄位:

Shopify path: order.name
Ragic field: 700002

但這種 UI 把所有風險留給使用者:

  • Field ID 可能不存在。
  • Field ID 存在,但屬於另一張表單。
  • 欄位是自動編號或唯讀 loaded field。
  • 欄位位於子表格,不能用一般 top-level payload 寫入。
  • 顯示名稱相同,但 type 或 write format 不相容。
  • 表單 URL 指到另一個 environment 或不同 sheet。

若 App 只把這些字串存進資料庫,Dry Run 看起來甚至可能正常;直到真正送出,Ragic 才回傳錯誤,或更糟地把資料寫進不該覆寫的欄位。

問題是怎麼推敲出來的?

Ragic 官方文件說明 Field ID 是程式辨認欄位的無歧義方式。讀取資料時也可以指定 naming=EID,讓 JSON property 使用 Field ID,而不是可能重複或改名的 display name。

所以 editor 的 source of truth 不該是人腦記得的欄位名稱,而應是帶有 Field ID 與寫入屬性的 schema snapshot。

來源專案定義的文件會為每張表單列出:

  • 表單名稱。
  • 表單 URL 與 API URL。
  • 主表單 key。
  • Field Name、Field ID、Type。
  • Writable、Write Format、Memo。
  • 子表格 heading 與 subtable key。

Parser 逐行辨認固定 heading 與 Markdown table。遇到 field row 時,會根據 Writable 與 Memo 判斷 readonly,從 type 判斷 linked field,從 memo 判斷 loaded field,並抽出單選選項與目前的 subtable key。

這份 normalized schema 才是後續 editor 的輸入。

最後採用的架構

整體流程分成四層:

Ragic 表單設計
  → 產生專案格式的 API 文件
  → parser 轉成 RagicParsedSheet[]
  → 篩選可寫入欄位
  → Shopify catalog × Ragic options
  → 商家建立 manual mapping

Parser 保留完整欄位資訊,但 editor 會再縮減:

  • writable 必須為 true。
  • isReadonly 必須為 false。
  • isLoadedField 必須為 false。
  • 選項顯示 Field Name、Field ID 與 type。

儲存時不能只信 UI 已篩選過。Server 會重新從同一份 parsed schema 建立 writableFieldsById,任何不在集合內的值都拒絕。

Shopify 端也不是自由輸入 path,而是由 order/customer field catalog 提供固定選項。兩邊都使用 allow-list,Mapping 才不會變成任意讀取 JSON path、任意寫入 Field ID 的通道。

關鍵實作

Parser 的核心不是複雜語法樹,而是小而嚴格的狀態機:

for (const rawLine of lines) {
  const line = rawLine.trim();

  if (isSheetHeading(line)) startSheet(line);
  else if (isApiUrl(line)) currentSheet.apiUrl = readValue(line);
  else if (isSubtableHeading(line)) activeSubtableKey = readKey(line);
  else if (isFieldHeader(line)) inFieldTable = true;
  else if (inFieldTable && isFieldRow(line)) {
    currentSheet.fields.push(parseFieldRow(line, activeSubtableKey));
  }
}

解析後的安全欄位可以長成:

type ParsedField = {
  id: string;
  name: string;
  type: string;
  writable: boolean;
  isReadonly: boolean;
  isLinkedField: boolean;
  isLoadedField: boolean;
  subtableKey?: string;
  options: string[];
};

Editor 選項使用 Field ID 當 value,display name 只用於說明:

const options = sheet.fields
  .filter((field) =>
    field.writable &&
    !field.isReadonly &&
    !field.isLoadedField
  )
  .map((field) => ({
    value: field.id,
    label: `${field.name} (${field.id}) · ${field.type}`,
  }));

文章示例使用假 Field ID 與假表單,不包含正式 Ragic URL、客戶 schema 或 API key。

實際驗證

來源 parser 測試已覆蓋:

  • 同一份文件解析多張 sheet。
  • Sheet name、form URL、API URL 與 main key。
  • 一般可寫文字欄位。
  • 自動編號與唯讀欄位。
  • 單選選項的抽取。
  • Link field 與 loaded field 的區別。
  • Subtable heading 後欄位會保留 subtable key。
  • 沒有合法 sheet section 時回傳空陣列。

Manual mapping 測試另外覆蓋:

  • 只接受 Shopify catalog 中存在的 path。
  • 只接受 parser 證明可寫的 Ragic Field ID。
  • 商家選擇不送出的欄位不會進 Mapping。
  • 缺少必要 unique key 時拒絕儲存。

這些測試證明 parser 與 editor 遵守目前文件 contract;它們不能證明任意版本、任意語言或人工修改過的 Ragic 文件都能被正確解析。

仍然存在的限制

第一個限制是格式耦合。Parser 依賴固定的繁體中文 heading、欄位順序與 Markdown table。只要文件產生器改成英文、調整欄名或改用 JSON,舊 parser 可能回傳空結果。較成熟的作法是替文件格式加明確 version,並以 fixture contract test 鎖住變更。

第二個限制是「看得到 subtable」不等於「會寫 subtable」。Ragic 官方的 create/update 說明要求 subtable row identity;新增 row 常以負數 temporary row ID 分組,更新既有 row 則要使用實際 row ID。目前案例的 payload builder 主要處理 top-level field,不能因 parser 保存了 subtableKey 就宣稱支援完整子表格同步。

第三個限制是 Link/Load 與公式。欄位可寫入不代表寫完後會自動得到預期的載入值;Ragic 的 doLinkLoaddoFormula、workflow 與 record lock 都是額外行為,應在 mapping policy 或 write profile 明確設定。

最後,Field ID 雖適合程式辨認,Ragic 官方也提醒不要任意修改。若 schema 真的調整,App 應將舊 Mapping 標記為 needs review,而不是只靠 display name 自動搬移。

可以延伸到哪些情境?

這個方法不只適用 Ragic:

  • 先把外部系統 schema 轉成版本化 normalized model。
  • UI 只呈現 server 證明可選的欄位。
  • 儲存時再次做 allow-list 驗證。
  • 顯示名稱供人理解,穩定 ID 才是程式識別。
  • 子表、關聯、公式與 workflow 使用不同 capability 表示,不用一個 writable 布林值概括。

下一篇會使用這份 Mapping 做受控預覽:寫入 Ragic 前先 Dry Run:資料同步工具如何讓商家安全確認?

參考資料

以上平台文件查核日期:2026-07-26。