เจาะลึกโครงสร้าง JSON ของ LINE Flex Message (Bubble & Box)

LINE Flex Message คือรูปแบบข้อความที่มีความยืดหยุ่นสูงที่สุดใน LINE Messaging API อนุญาตให้ Developer ออกแบบ Layout ที่ซับซ้อนได้เหมือน HTML/CSS แต่เขียนอยู่ในรูปแบบของ JSON Object

บทความนี้จะไม่อธิบายพื้นฐานการยิง API แต่จะเจาะลึกที่ “Structure & Hierarchy” ของ JSON Payload ซึ่งเป็นจุดที่ Developer มักทำผิดพลาดบ่อยที่สุด จนทำให้ข้อความไม่แสดงผล (Silent Error) หรือแสดงผลผิดเพี้ยน


1. The Architecture: โครงสร้างลำดับชั้น (Hierarchy)

ความเข้าใจผิดแรกคือการสับสนระหว่าง Container และ Component โครงสร้างของ Flex Message มีลำดับชั้นที่เข้มงวด ดังแผนภาพโครงสร้างข้อมูลด้านล่าง:

JSON Root Object
└── type: "flex"
    └── contents: {Container}
        │
        ├── Type A: "bubble" (กล่องข้อความเดียว)
        │   ├── styles
        │   ├── header (Box)
        │   ├── hero (Image/Video)
        │   ├── body (Box) *Required
        │   └── footer (Box)
        │
        └── Type B: "carousel" (สไลด์แนวนอน)
            └── contents: [ Bubble, Bubble, ... ]

Visualizing The Bubble Block

หนึ่ง Bubble จะถูกแบ่งออกเป็น 4 Blocks หลักตามแนวตั้ง (Vertical Stack) เสมอ ไม่สามารถสลับตำแหน่งได้:

+-------------------------------------------+
|  HEADER (Block)                           |
|  - เหมาะสำหรับ Title / หัวข้อ              |
+-------------------------------------------+
|  HERO (Block)                             |
|  - พื้นที่รูปภาพขนาดใหญ่ (Cover Image)      |
|  - Aspect Ratio ปรับได้                    |
+-------------------------------------------+
|  BODY (Block)                             |
|  - เนื้อหาหลัก (Main Content)              |
|  - รองรับ Nested Box (ซ้อน Layout)        |
+-------------------------------------------+
|  FOOTER (Block)                           |
|  - พื้นที่สำหรับปุ่ม Action / Copyright    |
+-------------------------------------------+

2. The Layout Engine: Box และ Direction

หัวใจสำคัญของการจัด Layout คือ “Box” ซึ่งทำหน้าที่เหมือน <div> ใน HTML โดย Box จะมี Property สำคัญที่สุดคือ layout ซึ่งกำหนดทิศทางการเรียงตัวของ Child Components (contents Array)

2.1 Vertical Box (Stacking)

layout: "vertical" คือค่า Default ของ Block ส่วนใหญ่ (Header, Body, Footer) คอมโพเนนต์ลูกจะเรียงต่อกันจากบนลงล่าง

JSON Structure:
{
  "type": "box",
  "layout": "vertical",
  "contents": [ A, B, C ]
}

Visual Result:
+-------+
|   A   |
+-------+
|   B   |
+-------+
|   C   |
+-------+

2.2 Horizontal Box (Row)

layout: "horizontal" ใช้เมื่อต้องการจัดวางของให้อยู่บรรทัดเดียวกัน (เช่น รูปโปรไฟล์คู่กับชื่อ หรือ ราคาคู่กับปุ่มซื้อ)

JSON Structure:
{
  "type": "box",
  "layout": "horizontal",
  "contents": [ A, B, C ]
}

Visual Result:
+-------+ +-------+ +-------+
|   A   | |   B   | |   C   |
+-------+ +-------+ +-------+

3. Deep Dive: เทคนิคการจัด Layout ที่มักเขียนผิด

ข้อผิดพลาด 80% ของการเขียน Flex Message เกิดจากการจัดการพื้นที่ (Spacing) และสัดส่วน (Flex Ratio) ใน Horizontal Box ไม่ถูกต้อง

Scenario: การทำ Layout แบบ “Label : Value”

ต้องการแสดงรายการข้อมูล เช่น “Status : Pending” โดยต้องการให้คำว่า Status ชิดซ้าย และ Pending ชิดขวา หรือแบ่งพื้นที่กันคนละ 30:70

Code ที่ถูกต้อง (Correct JSON Logic):

{
  "type": "box",
  "layout": "horizontal",
  "contents": [
    {
      "type": "text",
      "text": "Status",
      "flex": 2,           <-- กินพื้นที่ 2 ส่วน
      "color": "#aaaaaa"
    },
    {
      "type": "text",
      "text": "Pending",
      "flex": 8,           <-- กินพื้นที่ 8 ส่วน
      "align": "end",      <-- จัดชิดขวาภายในพื้นที่ตัวเอง
      "weight": "bold"
    }
  ]
}

Logic การคำนวณพื้นที่:

Component Flex Value Calculation Visual Width
Text (Status) 2 2 / (2+8) = 20% [Status..]
Text (Pending) 8 8 / (2+8) = 80% [.......Pending]

4. Debugging: วิเคราะห์ Error จาก JSON Structure

เมื่อ LINE Server ตอบกลับมาว่า 400 Bad Request มักเกิดจาก Syntax Error เหล่านี้:

4.1 Nested Array Trap (กับดักวงเล็บ)

ใน Box, property ชื่อ contents ต้องเป็น Array [] เสมอ แม้จะมีไอเท็มเดียวก็ตาม

❌ ผิด (Invalid):

"contents": {
    "type": "text",
    "text": "Hello"
}

✅ ถูก (Valid):

"contents": [
    {
        "type": "text",
        "text": "Hello"
    }
]

4.2 Layout Mismatch

ปัญหา: ใส่ Text ลงใน contents ของ Bubble โดยตรงไม่ได้

คำอธิบาย: Root ของ Bubble ต้องแบ่งเป็น Block (Header, Hero, Body, Footer) ก่อน แล้วใน Block นั้นถึงจะเป็น Box ที่ใส่ Text ได้

Strict Hierarchy Rule:
Bubble
 └── Body (Block)
      └── Box (Container)
           └── contents [ Text, Image, Button ] (Components)

5. Reference Guide: Properties ที่ต้องจำ

ตารางสรุป Property สำคัญที่ใช้ควบคุม Layout โดยไม่ต้องจำทุกตัว

Property ใช้กับ (Scope) หน้าที่ (Function)
flex Component ใน Box กำหนดอัตราส่วนความกว้าง/สูง (เหมือน CSS Flexbox flex-grow) ค่า default คือ 1
spacing Box (Parent) กำหนดระยะห่างระหว่าง Child Components (sm, md, lg)
margin Component (Child) กำหนดระยะห่างด้านบน (Top Margin) ของตัวเองเทียบกับตัวก่อนหน้า
align Text / Box จัดตำแหน่งแนวนอน (start, center, end)
gravity Text / Box จัดตำแหน่งแนวตั้ง (top, center, bottom) *ใช้ใน Box ที่มีความสูงเหลือ