وقتی یک برنامه باید Object، Array یا مقدارهای درون حافظه را در فایل ذخیره کند، از طریق API بفرستد یا داخل Cache قرار دهد، آن داده باید به یک قالب متنی قابل انتقال تبدیل شود. JSON Stringify نام رایج فرایندی است که ساختار داده را Serialize میکند و یک متن JSON میسازد. در JavaScript این کار معمولاً با JSON.stringify() انجام میشود، اما مفهوم آن در زبانهای دیگر با نامهایی مانند Serialization، Encoding یا Dump نیز دیده میشود.
پاسخ سریع: JSON Stringify چیست؟
JSON Stringify یعنی تبدیل Object، Array یا مقدارهای پشتیبانیشده به یک String با Syntax معتبر JSON. در این فرایند کلیدها و Stringها داخل کوتیشن دوتایی قرار میگیرند، ساختار تودرتو حفظ میشود و نوعهایی مانند Function، BigInt، Date، undefined یا Reference حلقوی طبق قواعد Runtime حذف، تبدیل یا باعث خطا میشوند. Stringify فقط تولید متن JSON است و بهتنهایی رمزنگاری، فشردهسازی یا اعتبارسنجی Schema انجام نمیدهد.
چرا باید داده را Stringify کنیم؟
Object در حافظه یک برنامه مستقیماً قابل ارسال روی شبکه یا ذخیره در فایل متنی نیست. گیرنده به مجموعهای از Byteها یا نویسهها نیاز دارد. Stringify یک نمایش استاندارد و مستقل از حافظه تولید میکند تا سیستم دیگری بتواند آن را Parse کند.
- ساخت Body درخواستهای HTTP و API
- ذخیره تنظیمات یا داده در فایل و Local Storage
- ثبت Log ساختاریافته
- ارسال پیام به Queue یا Worker
- ساخت Snapshot برای تست
- تولید کلید Cache در سناریوهای کنترلشده
Stringify مسیر برعکس Parse است: Parse از متن به داده میرود؛ Stringify از داده به متن JSON.
JSON.stringify در JavaScript چگونه کار میکند؟
امضای متد استاندارد JavaScript به این شکل است:
JSON.stringify(value, replacer, space)
| پارامتر | کاربرد | اختیاری |
|---|---|---|
value | مقداری که باید Serialize شود | خیر |
replacer | تابع یا آرایه برای انتخاب و تبدیل فیلدها | بله |
space | تعداد فاصله یا رشته تورفتگی برای خروجی خوانا | بله |
const user = {
id: 42,
name: "Sara",
active: true,
roles: ["editor", "author"]
};
const json = JSON.stringify(user);
console.log(json);
// {"id":42,"name":"Sara","active":true,"roles":["editor","author"]}
خروجی یک String است، نه Object. برای دسترسی دوباره به فیلدها باید آن را با JSON.parse() تبدیل کرد.
تبدیل انواع داده هنگام Stringify
JSON فقط String، Number، Boolean، null، Object و Array را تعریف میکند. در نتیجه همه نوعهای JavaScript نمایش مستقیم ندارند.
| نوع JavaScript | رفتار معمول در Object | رفتار معمول در Array |
|---|---|---|
| String، Number، Boolean | ذخیره میشود | ذخیره میشود |
null | null | null |
undefined | Property حذف میشود | به null تبدیل میشود |
| Function و Symbol | Property حذف میشود | به null تبدیل میشود |
NaN و Infinity | null | null |
| BigInt | بهطور پیشفرض TypeError ایجاد میکند | |
| Date | معمولاً با toJSON() به رشته ISO تبدیل میشود | |
JSON.stringify({
name: "Sara",
temp: undefined,
score: NaN,
run: () => true
});
// {"name":"Sara","score":null}
این رفتارها ممکن است باعث از دست رفتن داده شوند. قبل از Serialize باید قرارداد خروجی مشخص باشد و تست شود.
مثال صحیح و غلط
روش صحیح
const payload = {
orderId: "9007199254740993",
amount: "129.95",
createdAt: new Date().toISOString()
};
const body = JSON.stringify(payload);
در این مثال شناسه بزرگ و مقدار مالی بهصورت String مدل شدهاند تا دقت عددی از بین نرود و تاریخ نیز قرارداد صریح دارد.
روش پرریسک
const body = "{" +
'"name":"' + userInput + '",' +
'"active":true' +
"}";
ساخت دستی JSON با الحاق String میتواند کوتیشن، Backslash، خط جدید یا نویسههای کنترلی را خراب کند. استفاده از Serializer استاندارد Escape درست را انجام میدهد.
خروجی خوانا با پارامتر space
برای Debug یا فایل تنظیمات میتوان خروجی Pretty تولید کرد:
const pretty = JSON.stringify(user, null, 2);
console.log(pretty);
{
"id": 42,
"name": "Sara",
"active": true
}
برای انتقال شبکه معمولاً خروجی فشرده مناسبتر است، چون تورفتگی و خط جدید حجم را افزایش میدهند. برای نمایش انسانی، نسخه خوانا ارزش بیشتری دارد.
replacer برای حذف یا تبدیل فیلدها
پارامتر replacer میتواند آرایهای از نام کلیدها باشد:
const account = {
id: 7,
email: "sara@example.test",
passwordHash: "secret-hash",
role: "admin"
};
JSON.stringify(account, ["id", "email", "role"]);
// {"id":7,"email":"sara@example.test","role":"admin"}
یا میتواند تابعی باشد که برای هر کلید و مقدار اجرا میشود:
const json = JSON.stringify(account, (key, value) => {
if (key === "passwordHash") return undefined;
return value;
});
حذف فیلد محرمانه در replacer مفید است، اما دفاع اصلی نباید فقط به آن وابسته باشد. بهتر است DTO خروجی از ابتدا فقط فیلدهای مجاز را داشته باشد.
toJSON چیست؟
اگر Object متد toJSON() داشته باشد، Stringify از خروجی آن استفاده میکند. Date نمونه شناختهشده است.
const product = {
id: 10,
internalCost: 50,
price: 80,
toJSON() {
return { id: this.id, price: this.price };
}
};
JSON.stringify(product);
// {"id":10,"price":80}
این رفتار قدرتمند است، اما ممکن است هنگام Debug غافلگیرکننده باشد. Objectهایی که از کتابخانهها دریافت میشوند شاید toJSON() سفارشی داشته باشند.
خطای Converting circular structure to JSON
JSON ساختار درختی است و Reference حلقوی را نمایش نمیدهد. در این نمونه Object به خودش اشاره میکند:
const node = { id: 1 };
node.self = node;
JSON.stringify(node);
// TypeError: Converting circular structure to JSON
راهحل به هدف بستگی دارد: حذف Reference حلقوی، تبدیل آن به شناسه، طراحی DTO مستقل یا استفاده از replacer کنترلشده.
function circularSafeReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
};
}
JSON.stringify(node, circularSafeReplacer());
قرار دادن عبارت [Circular] برای Log مناسب است، اما لزوماً برای API قرارداد معتبری نیست.
Stringify کردن BigInt
BigInt نوع استاندارد JSON نیست و Stringify مستقیم آن خطا میدهد:
JSON.stringify({ id: 9007199254740993n });
// TypeError
یک راه رایج تبدیل صریح به String است:
const json = JSON.stringify(
{ id: 9007199254740993n },
(key, value) => typeof value === "bigint" ? value.toString() : value
);
// {"id":"9007199254740993"}
گیرنده باید بداند این String یک شناسه یا عدد بزرگ است و آن را طبق قرارداد پردازش کند.
JSON Serialize در Python
در Python تابع json.dumps() داده را به String و json.dump() آن را داخل فایل مینویسد.
import json
data = {
"id": 42,
"name": "سارا",
"active": True,
"roles": ["editor", "author"]
}
text = json.dumps(data, ensure_ascii=False)
print(text)
گزینه ensure_ascii=False اجازه میدهد نویسههای فارسی بهشکل خوانا باقی بمانند. برای خروجی فشرده میتوان Separatorها را تنظیم کرد:
compact = json.dumps(
data,
ensure_ascii=False,
separators=(",", ":")
)
نوعهایی مانند datetime و Decimal بهطور پیشفرض قابل Serialize نیستند و باید با Encoder یا تابع default تبدیل شوند.
JSON Serialize در C#
using System.Text.Json;
var order = new {
Id = 42,
Customer = "Sara",
Total = 129.95m
};
string json = JsonSerializer.Serialize(order);
برای خروجی خوانا:
var options = new JsonSerializerOptions {
WriteIndented = true
};
string json = JsonSerializer.Serialize(order, options);
نامگذاری Propertyها، Null handling، Enumها و Converterهای سفارشی باید در قرارداد API هماهنگ شوند.
JSON Serialize در Java
Java در کتابخانه استاندارد خود API واحدی مانند JavaScript ندارد و معمولاً از کتابخانههایی مانند Jackson یا Gson استفاده میشود. نمونه مفهومی با Jackson:
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(order);
تنظیمات Mapper باید مرکزی و کنترلشده باشند؛ تفاوت در Naming Strategy، Date format یا Null handling میتواند خروجی سرویسها را ناسازگار کند.
تفاوت Stringify، Serialize، Encode و Minify
| اصطلاح | معنا | مثال |
|---|---|---|
| Stringify | تبدیل مقدار به متن JSON | JSON.stringify() |
| Serialize | تبدیل داده به قالب قابل ذخیره یا انتقال | JSON، XML یا Binary |
| Encode | نمایش داده با قواعد یک Encoding یا قالب | UTF-8 یا Percent Encoding |
| Minify | حذف Whitespace غیرضروری از متن موجود | JSON فشرده |
| Encrypt | محرمانهسازی با کلید | AES؛ ارتباطی با Stringify ندارد |
خطاهای رایج JSON Stringify
- از دست رفتن undefined و Function: خروجی را با داده ورودی مقایسه و قرارداد را تست کنید.
- خطای BigInt: تبدیل صریح و مستند به String یا قالب دیگر انجام دهید.
- Reference حلقوی: DTO درختی بسازید یا Reference را با ID نمایش دهید.
- نشت اطلاعات حساس: Entity داخلی را مستقیم Serialize نکنید.
- تغییر Date: Time Zone و فرمت ISO را مشخص کنید.
- تکیه به ترتیب کلیدها: JSON از نظر معنایی نباید بر ترتیب Propertyها متکی باشد.
- حجم بسیار بزرگ: از Streaming serializer یا Pagination استفاده کنید.
امنیت هنگام Stringify
Stringify داده را امن یا محرمانه نمیکند. هر کسی که متن را ببیند میتواند آن را بخواند. Token، Password، Secret، کلید API، اطلاعات پرداخت و داده شخصی نباید بدون نیاز وارد خروجی شوند.
- برای پاسخ API از DTO یا View Model مجزا استفاده کنید.
- فیلدهای حساس را با Allowlist انتخاب کنید، نه Blacklist پراکنده.
- اندازه و عمق Object را قبل از Serialize محدود کنید.
- خروجی را در Context مناسب Escape کنید؛ JSON امن لزوماً برای قرارگیری مستقیم در HTML امن نیست.
- Logها را از داده حساس پاکسازی کنید.
عملکرد و JSONهای بزرگ
JSON.stringify() معمولاً کل String خروجی را در حافظه میسازد. برای Objectهای بزرگ، هم ساختار اصلی و هم متن نهایی همزمان حافظه مصرف میکنند. Pretty print نیز حجم را بیشتر میکند.
- فقط فیلدهای موردنیاز را Serialize کنید.
- برای مجموعههای بزرگ Pagination یا Streaming را بررسی کنید.
- از کپیهای غیرضروری String خودداری کنید.
- فشردهسازی شبکه را جداگانه و در لایه HTTP انجام دهید.
- بهینهسازی را با Benchmark روی داده واقعی بسنجید.
Best Practiceهای JSON Stringify
- قرارداد نوع، Null، تاریخ و عددهای بزرگ را مستند کنید.
- JSON را با الحاق دستی String نسازید.
- Entity پایگاه داده را مستقیم به Client نفرستید.
- برای خروجی پایدار، تست Snapshot یا Contract test داشته باشید.
- Pretty print را برای Debug نگه دارید و در Production فقط در صورت نیاز فعال کنید.
- پس از Stringify، در تستها خروجی را دوباره Parse و ساختار را بررسی کنید.
- برای امضا یا Hash، Canonicalization مشخص لازم است؛ Stringify معمولی تضمین Canonical JSON نمیدهد.
چکلیست قبل از Serialize
- آیا همه فیلدهای خروجی واقعاً لازماند؟
- آیا داده حساس حذف شده است؟
- آیا Date و Time Zone قرارداد مشخص دارند؟
- آیا BigInt و Decimal بدون کاهش دقت منتقل میشوند؟
- آیا Reference حلقوی وجود ندارد؟
- آیا اندازه خروجی قابل قبول است؟
- آیا گیرنده Schema و نوع فیلدها را میشناسد؟
- آیا تست Round-trip و خطاهای مرزی وجود دارد؟
Round-trip چیست و چگونه آن را تست کنیم؟
Round-trip یعنی داده را Stringify کنیم، خروجی را دوباره Parse کنیم و بررسی کنیم که ساختار مهم داده حفظ شده است. این تست برای همه نوعها برابر نیست، چون JSON برخی اطلاعات Runtime را نگه نمیدارد. برای مثال نمونه کلاس پس از Parse به Object ساده تبدیل میشود، Date ممکن است String شود و undefined از بین برود.
const original = {
id: 42,
tags: ["json", "api"],
active: true
};
const encoded = JSON.stringify(original);
const decoded = JSON.parse(encoded);
console.assert(decoded.id === original.id);
console.assert(decoded.tags.length === original.tags.length);
در تست حرفهای بهتر است بهجای مقایسه Reference یا Prototype، فیلدهای قراردادی و نوعهای مورد انتظار بررسی شوند. برای Date، Decimal یا BigInt نیز باید تبدیل رفت و برگشت اختصاصی تعریف شود.
ساخت Payload برای API
یکی از رایجترین کاربردهای Stringify، ساخت Body درخواست HTTP است. وجود JSON معتبر کافی نیست؛ Header، Encoding و قرارداد Endpoint نیز باید درست باشند.
const payload = {
title: "گزارش فروش",
published: false
};
const response = await fetch("/api/reports", {
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
body: JSON.stringify(payload)
});
قبل از ارسال، فقط فیلدهای موردنیاز را در Payload قرار دهید. فرستادن کل State رابط کاربری یا Entity کامل پایگاه داده میتواند هم حجم را بالا ببرد و هم اطلاعات داخلی را افشا کند. خطاهای پاسخ نیز باید جداگانه مدیریت شوند؛ موفق بودن Stringify تضمین نمیکند سرور Schema را بپذیرد.
ترتیب کلیدها و JSON Canonical
دو متن JSON میتوانند ترتیب کلید متفاوت ولی معنای یکسان داشته باشند. برای مثال {"a":1,"b":2} و {"b":2,"a":1} از دید مصرفکننده Object معمولاً برابرند. بنابراین مقایسه String خام برای تشخیص برابری معنایی قابل اتکا نیست.
این نکته برای Cache key، Hash و Signature مهم است. اگر دو سیستم باید دقیقاً یک Byte sequence تولید کنند، باید الگوریتم Canonicalization مشخصی داشته باشند که ترتیب کلیدها، نمایش عددها، Escape نویسهها و Whitespace را تعریف کند. مرتبسازی ساده کلیدهای سطح اول همیشه برای Objectهای تودرتو کافی نیست.
Stringify برای Local Storage و Cache
Local Storage در مرورگر فقط String ذخیره میکند؛ به همین دلیل Object باید قبل از ذخیره Stringify و هنگام خواندن Parse شود.
const preferences = {
theme: "dark",
pageSize: 25
};
localStorage.setItem("preferences", JSON.stringify(preferences));
const saved = localStorage.getItem("preferences");
const restored = saved ? JSON.parse(saved) : null;
داده ذخیرهشده ممکن است قدیمی، خراب یا توسط کاربر تغییر داده شده باشد. Parse باید با مدیریت خطا انجام شود و نسخه Schema نیز در نظر گرفته شود. Local Storage محل مناسبی برای Secret یا Token حساس نیست، چون اسکریپتهای همان Origin میتوانند به آن دسترسی داشته باشند.
طراحی Serializer قابل نگهداری
در پروژههای بزرگ، تنظیم Serialization نباید در هر Endpoint بهصورت متفاوت تکرار شود. Naming policy، Date format، Null handling، Enum representation و Converterهای سفارشی باید در یک لایه مشخص مدیریت شوند. در عین حال، یک تنظیم جهانی بسیار انعطافپذیر هم میتواند تغییرات ناخواسته در کل API ایجاد کند.
- برای قراردادهای عمومی، DTO نسخهبندیشده بسازید.
- Converter سفارشی را با ورودیهای مرزی تست کنید.
- تغییر فرمت تاریخ یا Enum را Breaking change در نظر بگیرید.
- نمونه خروجی واقعی را در مستندات و Contract test نگه دارید.
- خطاهای Serialization را با Context کافی Log کنید، اما داده حساس را ثبت نکنید.
چه زمانی JSON انتخاب مناسبی نیست؟
JSON برای تبادل داده متنی و سازگاری بین پلتفرمها بسیار مناسب است، اما همیشه بهترین گزینه نیست. برای داده دودویی بزرگ، فرمتهای Binary میتوانند فشردهتر و سریعتر باشند. برای Stream طولانی رکوردها، NDJSON پردازش مرحلهای را سادهتر میکند. برای داده جدولی حجیم، CSV یا فرمت ستونی ممکن است مناسبتر باشد.
انتخاب قالب باید بر اساس قرارداد، قابلیت Debug، اندازه، سرعت، پشتیبانی اکوسیستم و نیاز به Schema انجام شود. Stringify کردن هر ساختار به JSON فقط به دلیل راحتی میتواند در مقیاس بالا هزینه حافظه و شبکه ایجاد کند.
جمعبندی
JSON Stringify ساختار داده را به متن JSON قابل ذخیره و انتقال تبدیل میکند. این عملیات ساده به نظر میرسد، اما نوعهای پشتیبانینشده، حذف undefined، تبدیل Date، BigInt، Reference حلقوی، نشت فیلدهای حساس و مصرف حافظه میتوانند نتیجه را تغییر دهند. استفاده از Serializer استاندارد، DTO محدود، قرارداد صریح و تست Round-trip باعث میشود خروجی قابل پیشبینی و سازگار باقی بماند.