JSON Object هسته اصلی بسیاری از پیامهای JSON است. وقتی یک API اطلاعات کاربر، سفارش، محصول یا تنظیمات را برمیگرداند، معمولاً داده در قالب مجموعهای از ویژگیهای نامدار داخل آکولاد قرار میگیرد. این ساختار همان JSON Object است.
در ظاهر، Object فقط چند کلید و مقدار است؛ اما طراحی ضعیف آن میتواند باعث ناسازگاری میان کلاینت و سرور، خطاهای Validation، شکست Versioning، مشکلات امنیتی و Payloadهای سخت برای نگهداری شود. این راهنما ساختار Object، قواعد کلیدها، انواع مقدار، Object تودرتو، تفاوت با Array، کلیدهای تکراری، null، Schema، امنیت، عملکرد و بهترین روشهای طراحی را با مثالهای واقعی توضیح میدهد.
پاسخ سریع: JSON Object مجموعهای از جفتهای کلید و مقدار داخل آکولاد است. کلید باید String و داخل کوتیشن دوتایی باشد. مقدار میتواند String، Number، Boolean، null، Array یا Object دیگری باشد.
JSON Object چیست؟
Object برای نمایش یک موجودیت یا مجموعهای از ویژگیهای مرتبط استفاده میشود. مثال زیر یک کاربر را نمایش میدهد:
{
"id": 42,
"name": "Sara",
"email": "sara@example.test",
"isActive": true
}
هر عضو Object دو بخش دارد: نام ویژگی و مقدار آن. نام ویژگی با مقدار بهوسیله دونقطه جدا میشود و اعضای مختلف با ویرگول از هم جدا میشوند.
ساختار استاندارد Object
{
"key1": "value1",
"key2": 123,
"key3": true
}
- Object با
{شروع میشود. - کلید باید String باشد.
- کلید داخل کوتیشن دوتایی نوشته میشود.
- بین کلید و مقدار دونقطه قرار میگیرد.
- اعضا با ویرگول جدا میشوند.
- بعد از آخرین عضو ویرگول قرار نمیگیرد.
- Object با
}تمام میشود.
قواعد کلیدها در JSON Object
کلید همیشه String است
{
"100": "value"
}
در این مثال، "100" کلید متنی است، حتی اگر شبیه عدد باشد.
کلید بدون کوتیشن JSON معتبر نیست
{
name: "Sara"
}
این کد ممکن است Object Literal معتبر JavaScript باشد، اما JSON نیست. نسخه صحیح:
{
"name": "Sara"
}
کوتیشن تکی مجاز نیست
{
'name': 'Sara'
}
JSON استاندارد فقط کوتیشن دوتایی را برای کلید و String میپذیرد.
کلید خالی ممکن است اما مناسب نیست
{
"": "value"
}
این ساختار میتواند از نظر Syntax معتبر باشد، اما کلید بدون معنی طراحی ضعیفی است و مصرفکننده را به حدس زدن وادار میکند.
انواع مقدار در Object
| نوع | نمونه | کاربرد |
|---|---|---|
| String | "Sara" | متن و شناسه |
| Number | 42 | مقدار عددی |
| Boolean | true | وضعیت دوحالته |
| null | null | نبودن صریح مقدار |
| Array | ["admin"] | فهرست مرتب |
| Object | {"city":"Tehran"} | ساختار تودرتو |
{
"name": "Sara",
"age": 27,
"isActive": true,
"middleName": null,
"roles": ["editor", "author"],
"address": {
"city": "Tehran",
"postalCode": "1234567890"
}
}
Object تودرتو
وقتی مقدار یک کلید Object دیگری باشد، ساختار تودرتو ایجاد میشود:
{
"orderId": "ORD-1001",
"customer": {
"id": 42,
"name": "Sara",
"contact": {
"email": "sara@example.test",
"phone": null
}
},
"shippingAddress": {
"city": "Tehran",
"street": "Example Street"
}
}
این روش زمانی مناسب است که رابطه معنایی روشنی وجود داشته باشد. اطلاعات تماس زیرمجموعه مشتری است و آدرس ارسال بخش مستقل سفارش محسوب میشود.
چه زمانی تودرتویی بیش از حد است؟
- برای رسیدن به یک مقدار ساده مسیر بسیار طولانی لازم باشد.
- یک مدل در چند نقطه تکرار شود.
- Client مجبور به تعداد زیادی بررسی null باشد.
- Validation و Versioning سخت شود.
- ساختار فقط برای زیبایی ظاهری عمیق شده باشد.
بهترین روش: هر سطح تودرتویی باید یک رابطه معنایی واقعی را نمایش دهد.
Object و Array چه تفاوتی دارند؟
| ویژگی | Object | Array |
|---|---|---|
| نشانه | { } | [ ] |
| ساختار | کلید و مقدار | فهرست مقدارها |
| دسترسی | با نام کلید | با Index |
| ترتیب | نباید مبنای منطق باشد | بخشی از ساختار است |
| کاربرد | یک موجودیت | چند عضو مرتب |
یک کاربر با Object نمایش داده میشود:
{
"id": 42,
"name": "Sara"
}
چند کاربر با Array از Objectها:
[
{"id": 42, "name": "Sara"},
{"id": 43, "name": "Ali"}
]
آیا ترتیب کلیدها مهم است؟
منطق برنامه نباید به ترتیب اعضای Object وابسته باشد. دو Object زیر از نظر معنایی یک داده را نمایش میدهند:
{
"id": 42,
"name": "Sara"
}
{
"name": "Sara",
"id": 42
}
برخی Serializerها ترتیب را حفظ میکنند، اما این رفتار نباید بخشی از قرارداد باشد. اگر ترتیب اهمیت دارد، Array مناسبتر است.
کلید تکراری
{
"status": "draft",
"status": "published"
}
رفتار Parserها در این حالت یکسان نیست. بعضی آخرین مقدار، بعضی اولین مقدار و بعضی خطا تولید میکنند. بنابراین کلیدهای Object باید یکتا باشند و Validator بهتر است تکرار را رد کند.
نامگذاری کلیدها
| سبک | نمونه |
|---|---|
| camelCase | createdAt |
| snake_case | created_at |
| PascalCase | CreatedAt |
انتخاب سبک به اکوسیستم پروژه بستگی دارد، اما ثبات ضروری است.
نام روشن
{
"createdAt": "2026-08-06T10:30:00Z",
"isEmailVerified": true,
"totalAmount": 125000
}
نام مبهم
{
"data1": "2026-08-06T10:30:00Z",
"flag": true,
"value": 125000
}
Object خالی، null و فیلد حذفشده
{}
Object خالی معتبر است، اما با null یکسان نیست:
{
"profile": {}
}
{
"profile": null
}
| حالت | معنای احتمالی |
|---|---|
| Object خالی | ساختار وجود دارد اما عضو ندارد |
| null | مقدار وجود ندارد یا نامشخص است |
| فیلد حذفشده | ارسال نشده یا قابل اعمال نیست |
در PATCH، این تفاوت بسیار مهم است: حذف فیلد ممکن است به معنی «بدون تغییر» و null به معنی «پاک کردن مقدار» باشد.
Object در پاسخ API
{
"id": 42,
"name": "Sara",
"status": "active"
}
برای APIهای بزرگ، Envelope انعطاف بیشتری دارد:
{
"data": {
"id": 42,
"name": "Sara",
"status": "active"
},
"meta": {
"requestId": "REQ-2026-001"
}
}
Object برای Pagination
{
"items": [
{"id": 1, "name": "A"},
{"id": 2, "name": "B"}
],
"pagination": {
"page": 1,
"pageSize": 20,
"totalItems": 2,
"totalPages": 1
}
}
این ساختار از Array خالص توسعهپذیرتر است و امکان افزودن Metadata را فراهم میکند.
Object برای پاسخ خطا
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"details": {
"email": [
"Email is required.",
"Email format is invalid."
]
}
}
}
شکل خطا باید در همه Endpointها ثابت باشد تا کلاینت به شرطهای متعدد وابسته نشود.
Parse در JavaScript
const text = '{"id":42,"name":"Sara","active":true}';
try {
const user = JSON.parse(text);
if (
typeof user !== "object" ||
user === null ||
Array.isArray(user)
) {
throw new Error("Expected a JSON object");
}
console.log(user.name);
} catch (error) {
console.error("Invalid JSON object:", error);
}
Parse موفق فقط معتبر بودن Syntax را نشان میدهد. نوع و ساختار باید جداگانه بررسی شود.
ساخت Object در JavaScript
const user = {
id: 42,
name: "Sara",
active: true
};
const json = JSON.stringify(user, null, 2);
console.log(json);
استفاده از Serializer استاندارد مانع خطاهای کوتیشن و Escape میشود.
خواندن Object در Python
import json
text = '{"id": 42, "name": "Sara", "active": true}'
try:
user = json.loads(text)
if not isinstance(user, dict):
raise ValueError("Expected a JSON object")
print(user["name"])
except (json.JSONDecodeError, ValueError, KeyError) as error:
print("Invalid JSON object:", error)
Deserialize در C#
using System.Text.Json;
public sealed class UserDto
{
public int Id { get; init; }
public string Name { get; init; } = string.Empty;
public bool Active { get; init; }
}
var json = "{\"id\":42,\"name\":\"Sara\",\"active\":true}";
try
{
var user = JsonSerializer.Deserialize<UserDto>(json);
if (user is null)
{
throw new JsonException("Object was null.");
}
Console.WriteLine(user.Name);
}
catch (JsonException exception)
{
Console.WriteLine(exception.Message);
}
Object و JSON Schema
{
"type": "object",
"properties": {
"id": {
"type": "integer",
"minimum": 1
},
"name": {
"type": "string",
"minLength": 1
},
"active": {
"type": "boolean"
}
},
"required": ["id", "name"],
"additionalProperties": false
}
این Schema نوع ریشه، نوع فیلدها، فیلدهای اجباری و سیاست کلید اضافی را مشخص میکند.
کلید اضافی را قبول کنیم؟
رد کردن کلید اضافی
- اشتباه تایپی سریع پیدا میشود.
- قرارداد دقیقتر است.
- Mass Assignment محدودتر میشود.
پذیرفتن کلید اضافی
- سازگاری روبهجلو بهتر است.
- افزودن فیلد جدید کمتر کلاینت قدیمی را میشکند.
انتخاب باید آگاهانه باشد. فیلد ناشناخته نباید خودکار وارد مدل حساس شود.
امنیت JSON Object
Mass Assignment
{
"displayName": "Sara",
"isAdmin": true,
"accountBalance": 999999
}
Bind مستقیم به Entity دیتابیس خطرناک است. DTO باید فقط فیلدهای مجاز را تعریف کند.
Prototype Pollution
در JavaScript، Merge کردن Object کنترلنشده با کلیدهایی مانند __proto__ میتواند خطرناک باشد. از روشها و کتابخانههای امن استفاده کنید.
عمق و اندازه
Object بسیار عمیق یا بزرگ میتواند منابع Parser و Validator را مصرف کند. محدودیت Body، عمق و تعداد اعضا ضروری است.
Log داده حساس
Object ممکن است Token، اطلاعات شخصی یا مالی داشته باشد. Log باید Mask شود و سیاست نگهداری مشخص داشته باشد.
عملکرد Objectهای بزرگ
- زمان Parse بیشتر
- مصرف حافظه بالاتر
- Validation پرهزینهتر
- خوانایی کمتر
- Diff و Cache سختتر
بهبود عملکرد
- فقط فیلدهای لازم را ارسال کنید.
- داده مستقل را به Endpoint جدا منتقل کنید.
- عمق غیرضروری را حذف کنید.
- برای فهرست از Pagination استفاده کنید.
- Payload واقعی را Benchmark کنید.
Object تخت یا تودرتو؟
{
"userId": 42,
"userName": "Sara",
"addressCity": "Tehran"
}
{
"user": {
"id": 42,
"name": "Sara"
},
"address": {
"city": "Tehran"
}
}
ساختار تودرتو رابطه را بهتر نشان میدهد؛ ساختار تخت دسترسی سادهتری دارد. انتخاب باید براساس مدل، Query و نیاز کلاینت انجام شود.
Object بهعنوان Map
{
"usersById": {
"42": {"name": "Sara"},
"43": {"name": "Ali"}
}
}
این مدل برای Lookup مناسب است، اما ترتیب، Pagination و Schema کلیدهای Dynamic را پیچیده میکند.
کلید Dynamic یا Array؟
{
"metrics": {
"2026-08-01": 120,
"2026-08-02": 135
}
}
{
"metrics": [
{"date": "2026-08-01", "value": 120},
{"date": "2026-08-02", "value": 135}
]
}
Object برای Lookup مستقیم مناسب است؛ Array برای Sort، Filter، Metadata و Pagination انعطاف بیشتری دارد.
Versioning در Object
تغییر کمخطر
- افزودن فیلد اختیاری
- افزودن Metadata
- افزودن مقدار قابل توسعه
تغییر پرخطر
- حذف فیلد
- تغییر نام کلید
- تغییر نوع مقدار
- تبدیل Object به Array
- تغییر معنای null
این تغییر ناسازگار است:
{"tags": "json"}
{"tags": ["json", "api"]}
اگر فیلد چندمقداری است، از ابتدا Array تعریف شود.
اشتباهات رایج
- کلید بدون کوتیشن
- کوتیشن تکی
- ویرگول پایانی
- کلید تکراری
- نام مبهم
- تغییر نوع فیلد
- تودرتویی زیاد
- وابستگی به ترتیب کلیدها
- Object بهجای Array برای داده مرتب
- Bind مستقیم ورودی به Entity
بهترین روشها
- نام کلیدها را روشن انتخاب کنید.
- یک سبک نامگذاری ثابت داشته باشید.
- کلید تکراری را رد کنید.
- نوع فیلد را ثابت نگه دارید.
- معنای null و فیلد حذفشده را مستند کنید.
- تودرتویی را فقط برای رابطه واقعی استفاده کنید.
- Schema یا DTO داشته باشید.
- سیاست کلید اضافی را مشخص کنید.
- عمق و اندازه را محدود کنید.
- از Serializer استاندارد استفاده کنید.
چکلیست نهایی
- آیا ریشه باید Object باشد؟
- آیا همه کلیدها کوتیشن دوتایی دارند؟
- آیا کلید تکراری وجود ندارد؟
- آیا نامها معنی روشن دارند؟
- آیا نوع فیلدها ثابت است؟
- آیا null تعریف شده است؟
- آیا عمق منطقی است؟
- آیا فیلد حساس یا غیرمجاز وجود ندارد؟
- آیا Schema با قرارداد هماهنگ است؟
- آیا تغییرات نسخه جدید سازگارند؟
دسترسی امن به ویژگیهای Object
پس از Parse شدن JSON، دسترسی مستقیم به ویژگیها همیشه امن نیست؛ چون ممکن است فیلد حذف شده، null باشد یا نوع متفاوتی داشته باشد. در JavaScript استفاده از Optional Chaining و مقدار پیشفرض میتواند خطاهای Runtime را کاهش دهد:
const city = user.address?.city ?? "Unknown";
const roles = Array.isArray(user.roles) ? user.roles : [];
این کد جای Validation را نمیگیرد. Optional Chaining فقط از Crash جلوگیری میکند؛ اما اگر قرارداد میگوید address اجباری است، نبودن آن باید بهعنوان خطای داده گزارش شود.
تفاوت وجود کلید و مقدار undefined
در JavaScript پس از Parse کردن JSON، مقدار undefined از خود JSON نمیآید؛ چون JSON چنین نوعی ندارد. بااینحال دسترسی به کلیدی که وجود ندارد، undefined برمیگرداند:
const object = JSON.parse('{"name":"Sara"}');
console.log(object.name); // Sara
console.log(object.email); // undefined
console.log("email" in object); // false
اگر لازم است میان کلید حذفشده و کلید موجود با null تفاوت بگذارید، فقط بررسی مقدار کافی نیست و باید وجود Property را نیز بررسی کنید.
Merge کردن Objectها و خطر بازنویسی ناخواسته
در بسیاری از برنامهها، Objectهای ورودی با تنظیمات پیشفرض یا داده موجود Merge میشوند. این عملیات میتواند فیلدها را ناخواسته بازنویسی کند:
const defaults = {
theme: "light",
isAdmin: false
};
const input = {
isAdmin: true
};
const result = {
...defaults,
...input
};
در این مثال، مقدار ورودی بر مقدار پیشفرض غلبه میکند. اگر input از کاربر آمده باشد و فیلد isAdmin نباید قابل تغییر باشد، این Merge یک مشکل امنیتی ایجاد میکند.
راهحل بهتر، انتخاب صریح فیلدهای مجاز است:
const result = {
theme:
typeof input.theme === "string"
? input.theme
: defaults.theme,
isAdmin: defaults.isAdmin
};
مقایسه دو JSON Object
مقایسه مستقیم Stringها روش قابل اعتمادی برای بررسی برابری Objectها نیست، چون ترتیب کلیدها و فاصلهها ممکن است متفاوت باشد:
{"id":42,"name":"Sara"}
{
"name": "Sara",
"id": 42
}
این دو متن متفاوتاند، اما میتوانند همان داده را نمایش دهند. برای مقایسه معنایی، باید داده Parse و سپس با الگوریتم Deep Equality بررسی شود. اگر هدف امضا یا Hash پایدار است، به Canonicalization مشخص نیاز دارید تا ترتیب کلیدها و نمایش عددها ثابت شود.
نکته:
JSON.stringify(a) === JSON.stringify(b)فقط وقتی قابل اتکاست که ترتیب Propertyها و فرایند Serialization در هر دو طرف دقیقاً کنترل شده باشد.
Canonical JSON و خروجی پایدار
در بعضی کاربردها مانند امضای دیجیتال، Cache Key یا Deduplication، لازم است یک Object همیشه به متن یکسان تبدیل شود. JSON معمولی این تضمین را بهصورت عمومی نمیدهد، چون ترتیب کلیدها ممکن است متفاوت باشد.
فرایند Canonicalization معمولاً این موارد را مشخص میکند:
- ترتیب ثابت کلیدها
- نمایش استاندارد Number
- Encoding ثابت
- قواعد مشخص Escape
- حذف فاصلههای غیرضروری
برای کاربرد امنیتی نباید Canonicalization دستساز و مبهم طراحی کرد. همه طرفها باید دقیقاً از یک قرارداد و کتابخانه سازگار استفاده کنند.
تبدیل Object به Query Parameter مناسب نیست
گاهی توسعهدهندگان یک Object پیچیده را مستقیماً داخل Query String قرار میدهند. این کار با مشکلات طول URL، Encoding، Log شدن داده و تفسیر متفاوت Arrayها روبهرو میشود.
برای فیلتر ساده، Query Parameterهای صریح مناسباند:
GET /users?status=active&page=1&pageSize=20
برای فیلتر پیچیده یا داده حساس، استفاده از Body JSON در یک Endpoint مناسب میتواند طراحی بهتری باشد. انتخاب متد و مسیر باید با معنا و قواعد Cache هماهنگ باشد.
نمونه طراحی یک Object حرفهای برای سفارش
{
"id": "ORD-1001",
"status": "pending",
"customer": {
"id": "USR-42",
"displayName": "Sara"
},
"items": [
{
"productId": "PRD-10",
"title": "Keyboard",
"quantity": 2,
"unitPrice": {
"amount": 895000,
"currency": "IRR"
}
}
],
"totals": {
"subtotal": 1790000,
"discount": 0,
"payable": 1790000,
"currency": "IRR"
},
"createdAt": "2026-08-06T10:30:00Z"
}
چند تصمیم مهم در این طراحی دیده میشود:
- شناسهها String هستند تا دقت عددی میان زبانها مسئله نشود.
- مبلغ با واحد مشخص و Integer ذخیره شده است.
- customer یک Snapshot حداقلی از اطلاعات مورد نیاز سفارش است.
- items بهدلیل چندمقداری بودن Array است.
- تاریخ با Format صریح و منطقه زمانی مشخص ارسال شده است.
- نوع هر فیلد در تمام پاسخها باید ثابت بماند.
Validation خطاهای Object را چگونه گزارش کنیم؟
پیام خطا باید مسیر دقیق فیلد را نشان دهد تا توسعهدهنده یا کلاینت بتواند مشکل را سریع پیدا کند:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body is invalid.",
"issues": [
{
"path": "customer.email",
"code": "INVALID_FORMAT",
"message": "Email format is invalid."
},
{
"path": "items[0].quantity",
"code": "OUT_OF_RANGE",
"message": "Quantity must be greater than zero."
}
]
}
}
گزارش فقط «Invalid JSON» برای خطای معنایی کافی نیست. باید میان خطای Syntax، خطای نوع، فیلد اجباری، مقدار خارج از محدوده و مجوز نامعتبر تفاوت گذاشت.
آیا Object باید همه اطلاعات موجودیت را برگرداند؟
خیر. پاسخ API باید فقط اطلاعات مورد نیاز همان Use Case را برگرداند. ارسال Entity کامل میتواند Payload را بزرگ کند، داده حساس را افشا کند و کلاینت را به ساختار داخلی دیتابیس وابسته سازد.
برای مثال، پاسخ عمومی کاربر نباید شامل Hash رمز عبور، Token داخلی، یادداشت مدیریتی یا تنظیمات امنیتی باشد. مدل خروجی باید مستقل و حداقلی طراحی شود.
تست JSON Object
تست یک Object فقط با یک نمونه موفق کامل نمیشود. حداقل این حالتها باید بررسی شوند:
- Object معتبر با همه فیلدها
- Object معتبر با فیلدهای اختیاری حذفشده
- فیلد اجباری حذفشده
- نوع اشتباه برای هر فیلد
- null در فیلدی که null نمیپذیرد
- کلید اضافی
- Object بسیار عمیق یا بزرگ
- String خالی و String بسیار طولانی
- عدد خارج از محدوده
- کلید تکراری در ورودی خام
این تستها باید هم در سطح Validator و هم در سطح رفتار Endpoint اجرا شوند.
جمعبندی
JSON Object برای نمایش موجودیتها و ویژگیهای نامدار استفاده میشود. هر عضو آن یک کلید String و یک مقدار معتبر JSON دارد. Object حرفهای باید علاوه بر Syntax صحیح، نامگذاری روشن، نوعهای ثابت، معنای مشخص برای null، عمق منطقی، Schema قابل اعتبارسنجی و سیاست امنیتی مناسب داشته باشد.
استفاده از Serializer استاندارد، DTO یا Schema، محدودیت اندازه و طراحی سازگار با نسخههای آینده، Object را از یک ساختار ساده به یک قرارداد داده قابل اعتماد تبدیل میکند.