JSON Toolkit

نکات کلیدی طراحی قرارداد داده API در JSON

جمع‌بندی: شش چیز پس از انتشار بسیار سخت تغییر می‌کنند: پاکت پاسخ، قالب خطا، شکل صفحه‌بندی، نمایش شناسه و زمان، معناشناسی null در برابر غایب، و تکامل نسخه.

متوسطحدود 13 دقیقه مطالعهانتشار 2026-10-30به‌روزرسانی 2026-10-30
تصویر جلد نکات کلیدی طراحی قرارداد داده API در JSON

جمع‌بندی: پاکت پاسخ، قالب خطا، نمایش شناسه و زمان، و معناشناسی null در برابر غایب را در قرارداد قفل کنید، پیش از بحث بر سر جزئیات صفحه‌بندی و نام‌گذاری؛ همیشه سطح بالا را در پاکت data بپیچید تا صفحه‌بندی و شناسه ردیابی بعداً بدون شکستن فراخوان‌های موجود اضافه شوند.

در طراحی API پاسخ استاندارد وجود ندارد، اما تصمیم‌های «سخت پس‌گرفتنی» وجود دارند که بهتر است پیش از نوشتن نخستین خط کد درباره‌شان فکر کنید.

۱. پاکت پاسخ یا نه

سه سبک:

// Style A: bare response
{ "id": 1, "name": "张三" }

// Style B: data envelope
{ "data": { "id": 1, "name": "张三" } }

// Style C: data + metadata
{ "data": { "id": 1, "name": "张三" }, "meta": { "requestId": "a1b2" } }
سبک مزیت‌ها عیب‌ها
پاسخ خالی ساده‌ترین، یک لایه کمتر برای فرانت‌اند نمی‌تواند فراداده ضمیمه کند؛ آرایه سطح بالا ریسک hijacking و تکامل دارد
پاکت داده افزودن meta / errors / links آسان یک سطح تودرتویی بیشتر، فرانت‌اند باید res.data.data بنویسد
داده + فراداده بهترین قابلیت مشاهده نیاز به توافق سخت بر اینکه کدام فیلدها در meta باشند

توصیه: برای فهرست‌ها و جزئیات یکسان از پاکت data استفاده کنید و هرگز سطح بالا را آرایه نکنید. آن‌گاه افزودن صفحه‌بندی یا شناسه ردیابی بعداً هرگز قرارداد را نمی‌شکند.

۲. قالب خطا: از RFC 9457 کپی کنید

اختراع ساختار خطای خودتان منبع عظیم هزینه همکاری است. RFC 9457 (که جایگزین RFC 7807 است) application/problem+json را تعریف می‌کند:

{
  "type": "https://bbdrug.com/problems/insufficient-balance",
  "title": "余额不足",
  "status": 422,
  "detail": "账户余额 12.00 元,本次扣款需要 30.00 元",
  "instance": "/accounts/A1001/payments/p-889",
  "balance": "12.00",
  "required": "30.00"
}
فیلد معنا الزامی
type URI پایدار نوع مشکل توصیه‌شده
title خلاصه کوتاه خوانا برای انسان توصیه‌شده
status کد وضعیت HTTP توصیه‌شده
detail توضیح مشخص این بار چه رخ داد اختیاری
instance نمونه منبعی که مشکل داشت اختیاری
فیلدهای افزونه فیلدهای کسب‌وکار دلخواه اختیاری

فیلدهای افزونه (مانند balance / required بالا) توسط مشخصات مجازند و فرانت‌اند می‌تواند برای پیام‌های دقیق از آن‌ها استفاده کند نه تجزیه متن detail.

۳. صفحه‌بندی: cursor بر offset پیروز می‌شود

// Offset pagination
{ "data": [], "page": { "offset": 40, "limit": 20, "total": 137 } }

// Cursor pagination
{ "data": [], "page": { "nextCursor": "eyJpZCI6MTAwfQ", "hasMore": true } }
بُعد صفحه‌بندی offset صفحه‌بندی cursor
سختی پیاده‌سازی کم (LIMIT/OFFSET) متوسط (کلید مرتب‌سازی پایدار لازم دارد)
عملکرد در صفحات عمیق ضعیف (هر چه offset بزرگ‌تر، کندتر) خوب
وقتی داده تغییر می‌کند تکراری یا گم‌شده پایدار
پرش به صفحه ممکن ممکن نیست
نیاز به شمارش کل شمارش اضافه لازم دارد لازم ندارد

توصیه: برای جدول‌های مدیریتی (که پرش صفحه و مجموع می‌خواهند) صفحه‌بندی offset و برای فیدهای بی‌نهایت کاربر-facing صفحه‌بندی cursor استفاده کنید.

۴. شناسه‌ها و زمان

۴.۱ همیشه برای شناسه‌ها از رشته استفاده کنید

استدلال در مقاله نوع‌ها پوشش داده شده: در سمت جاوااسکریپت، مقادیر بالای 2^53 بی‌صدا دقت را از دست می‌دهند. حتی اگر شناسه فعلی عدد صحیح کوچک خودافزا است، استفاده از رشته برای مهاجرت بعدی به شناسه‌های snowflake جا باز می‌کند.

{ "id": "1000000000000000001" }

۴.۲ برای زمان از رشته‌های RFC 3339 استفاده کنید

{
  "createdAt": "2026-10-30T10:41:45+08:00",
  "updatedAt": "2026-10-30T02:41:45Z"
}
  • از عدد مهر زمانی میلی‌ثانیه‌ای استفاده نکنید: خواندنش سخت است و واحد (ثانیه/میلی‌ثانیه) مکرر اشتباه گرفته می‌شود؛
  • منطقه زمانی را صریح مشخص کنید: یا با Z یا با offset؛ هرگز «زمان محلی بدون منطقه» استفاده نکنید؛
  • فیلدها را یکدست xxxAt نام‌گذاری کنید تا در یک نگاه روشن باشد یک نقطه در زمان است.

۵. null و غایب

باید یکی را انتخاب و در مستندات بنویسید:

رویکرد بیان «بدون مقدار» بیان «تغییر نده»
رویکرد A مقدار فیلد null است حذف فیلد در درخواست
رویکرد B فیلد وجود ندارد حذف فیلد در درخواست

مشکل رویکرد A این است که «حذف شده» و «به‌صورت null فرستاده شده» در PATCH معنای متفاوتی دارند، که آسان اشتباه تفسیر می‌شود؛ مشکل رویکرد B این است که JSON.stringify فرانت‌اند فیلدهایی با مقدار undefined را حذف می‌کند، که تصادفاً با معناشناسی می‌خواند.

توصیه عملی:

  • برای بدنه پاسخ از رویکرد A استفاده کنید (null صریحاً یعنی بدون مقدار و فرانت‌اند نیازی به تشخیص undefined ندارد)؛
  • برای بدنه درخواست (به‌ویژه PATCH) از رویکرد B، به‌همراه Merge Patch یا JSON Patch برای صریح کردن معناشناسی.

۶. نام‌گذاری و ساختار فیلد

تصمیم انتخاب‌های رایج هشدار
سبک نام‌گذاری camelCase (اکوسیستم JS) / snake_case (Python، پایگاه‌داده) یکدست در پروژه، مخلوط نکنید
مقادیر enum حروف کوچک با زیرخط، "pending_review" از عدد استفاده نکنید؛ خواندنش سخت است
فیلدهای بولی isActive / enabled از status: 0/1 استفاده نکنید
فیلدهای آرایه اسم جمع، items برای آرایه خالی [] برگردانید، هرگز null
عمق تودرتویی بیش از ۳ سطح نه عمیق‌تر، تقسیمش کنید یا پارامتر expand بدهید

برگرداندن [] به‌جای null برای آرایه‌های خالی بسیار مهم است: data.items.map(...) در فرانت‌اند به‌خاطر null کرش نمی‌کند.

۷. راهبرد تکامل

تغییر شکستن سازگاری چگونه برخورد کنیم
افزودن فیلد اختیاری نه مستقیم منتشر کنید
افزودن فیلد الزامی بله مقدار پیش‌فرض بدهید، یا با نسخه منتشر کنید
حذف فیلد بله اول منسوخ علامت بزنید، فراخوان‌ها را زیر نظر بگیرید، سپس حذف
تغییر نوع فیلد بله نام فیلد جدید اضافه و در گذار هم‌زمان بنویسید
تغییر مجموعه مقدار enum بستگی دارد کلاینت باید برای مقادیر ناشناخته جایگزین داشته باشد
تغییر معناشناسی فیلد بله آن را فیلد جدید بدانید

سه قاعده انضباط:

  1. کلاینت‌ها باید نسبت به فیلدهای ناشناخته تحمل داشته باشند (خودتان را با additionalProperties: false در تنگنا نگذارید)؛
  2. کلاینت‌ها باید شاخه جایگزین برای مقادیر enum ناشناخته داشته باشند؛
  3. فیلدهای منسوخ را دست‌کم یک نسخه اصلی نگه دارید و با Deprecation در سرآیند پاسخ یا مستندات علامت بزنید.

۸. یک مثال کامل

{
  "data": [
    {
      "id": "1001",
      "name": "JSON 权威指南",
      "price": "89.50",
      "tags": ["json", "spec"],
      "publishedAt": "2026-09-01T00:00:00+08:00",
      "author": null
    }
  ],
  "page": { "nextCursor": null, "hasMore": false },
  "meta": { "requestId": "req-7f3a", "tookMs": 12 }
}

این پاسخ همه قراردادهای بالا را بازتاب می‌دهد: پاکت data، شناسه‌های رشته‌ای، مبلغ‌ها به‌صورت رشته، مهر زمانی با منطقه زمانی، null یعنی گم‌شده، آرایه خالی، صفحه‌بندی cursor، و meta برای اطلاعات قابلیت مشاهده.

۹. چک‌لیست

  • [ ] سطح بالا آرایه نیست؛ یکسان از پاکت data استفاده می‌شود؛
  • [ ] پاسخ‌های خطا از RFC 9457 پیروی می‌کنند و type پایدار دارند؛
  • [ ] شکل صفحه‌بندی با سناریوی کسب‌وکار می‌خواند؛
  • [ ] شناسه‌ها رشته‌اند و زمان از RFC 3339 استفاده می‌کند؛
  • [ ] معناشناسی null و غایب در مستندات نوشته شده است؛
  • [ ] سبک نام‌گذاری در پروژه یکدست است و enumها از رشته حروف کوچک استفاده می‌کنند؛
  • [ ] کلاینت برای فیلدهای ناشناخته و مقادیر enum ناشناخته جایگزین دارد؛
  • [ ] تغییرات شکننده از نسخه‌بندی یا گذار هم‌زمان‌نویسی عبور می‌کنند.

پرسش‌های متداول

چگونه یک API JSON قابل تکامل طراحی کنم؟

فهرست‌ها و جزئیات را یکسان در پاکت data بپیچید و هرگز سطح بالا را آرایه نکنید، تا صفحه‌بندی و شناسه ردیابی بدون شکستن قرارداد اضافه شوند. از شناسه رشته‌ای و مهر زمانی RFC 3339 استفاده کنید و معناشناسی null در برابر غایب را در مستندات بنویسید. فیلدهای جدید را اختیاری و از نظر معنایی مستقل نگه دارید و به کلاینت‌ها برای فیلدها و مقادیر enum ناشناخته جایگزین بدهید.

چرا قالب خطای سفارشی اختراع نکنیم؟

اختراع ساختار خطای خودتان منبع عظیم هزینه همکاری است. از application/problem+json در RFC 9457 کپی کنید، مشکل را با type پایدار شناسایی کنید و فیلدهای افزونه مانند balance و required را مجاز بگذارید، تا فرانت‌اند بتواند پیام دقیق بسازد بدون تجزیه متن detail.

آیا هر endpoint باید صفحه‌بندی cursor داشته باشد؟

نه. جدول‌های مدیریتی پرش صفحه و مجموع می‌خواهند، پس offset مناسب‌تر است؛ فیدهای بی‌نهایت کاربر-facing باید cursor داشته باشند که در صفحات عمیق پایدار می‌ماند و هنگام تغییر داده از تکراری یا شکاف پرهیز می‌کند. بر اساس اینکه سناریو پرش صفحه و مجموع می‌خواهد یا نه انتخاب کنید.

منابع

ابزارهای مرتبط

مقالات مرتبط

شش نوع، دقت اعداد و دام‌های رمزگذاری

JSON فقط شش نوع دارد، اما هر یک از آن‌ها می‌تواند داده شما را بی‌صدا تغییر دهد. این مقاله دام‌های اعداد، رشته‌ها و null را یک‌جا پوشش می‌دهد.

مقدماتیحدود 12 دقیقه مطالعهبه‌روزرسانی 2026-10-30

JSON Pointer، Patch و Merge Patch

سه مشخصات کوتاه یک پروتکل تغییر می‌سازند: Pointer مکان‌یابی می‌کند، Patch دقیق ویرایش می‌کند، Merge Patch جزئی ادغام می‌کند. انتخاب اشتباه به آرایه‌ها و هم‌زمانی آسیب می‌زند.

متوسطحدود 12 دقیقه مطالعهبه‌روزرسانی 2026-10-30

مدیریت JSON برای پیکربندی، fixture و داده محلی

پیکربندی باید لایه‌بندی و اعتبارسنجی شود؛ fixture باید قطعی، خوانا و قابل diff باشد. اگر این دو نوع فایل بد مدیریت شوند، هزینه عیب‌یابی مدت طولانی بالا می‌ماند.

مقدماتیحدود 10 دقیقه مطالعهبه‌روزرسانی 2026-10-30