وقتی داده JSON چندین سطح Object و Array دارد، دسترسی مستقیم به فیلدها، مقایسه رکوردها، ذخیرهسازی در جدول یا ارسال به ابزارهای سادهتر دشوار میشود. JSON Flatten فرایندی است که مسیر هر مقدار نهایی را به یک کلید تخت تبدیل میکند تا ساختار تودرتو به مجموعهای از زوجهای کلید و مقدار تبدیل شود. این کار برای گزارشگیری، تبدیل داده، ساخت CSV، ایندکسگذاری و بررسی تفاوتها مفید است، اما اگر قواعد مسیر و Arrayها از ابتدا مشخص نباشند، بازگردانی داده میتواند مبهم یا حتی غیرممکن شود.
Quick Answer: JSON Flatten ساختار تودرتوی JSON را به کلیدهایی مبتنی بر مسیر تبدیل میکند؛ برای مثال user.profile.name به مقدار نام اشاره میکند. برای Flatten قابل بازگشت باید جداکننده مسیر، شیوه نمایش Array، Escape کردن کلیدهای دارای نقطه و رفتار با مقدارهای null یا Object خالی از قبل تعریف شود.JSON Flatten چیست؟
JSON Flatten یا تختکردن JSON یعنی تبدیل یک درخت داده به یک سطح از کلیدها. در ساختار اصلی، هر Object میتواند Object یا Array دیگری داشته باشد. در خروجی تخت، محل هر مقدار با یک مسیر بیان میشود. این مسیر معمولاً از نام کلیدهای والد و فرزند تشکیل شده و با نقطه، اسلش یا قالبی مانند براکت جدا میشود.
هدف Flatten حذف معنا یا تبدیل نوع داده نیست؛ هدف، تغییر نمایش ساختار است. String باید String باقی بماند، Number نباید به متن تبدیل شود و Boolean یا null نیز باید نوع خود را حفظ کند. اگر ابزار Flatten همه چیز را به String تبدیل کند، علاوه بر تختکردن، یک تبدیل نوع نیز انجام داده است و بازگردانی دقیق دشوارتر میشود.
{
"user": {
"id": 42,
"profile": {
"name": "Sara",
"active": true
}
}
}نمایش تخت همین داده با جداکننده نقطه میتواند چنین باشد:
{
"user.id": 42,
"user.profile.name": "Sara",
"user.profile.active": true
}JSON Flatten چگونه کار میکند؟
الگوریتم معمول Flatten از ریشه شروع میکند و ساختار را بهصورت بازگشتی پیمایش میکند. هر بار که به Object میرسد، نام کلید به مسیر فعلی افزوده میشود. وقتی مقدار نهایی مانند String، Number، Boolean یا null دیده شد، مسیر کامل بهعنوان کلید خروجی ثبت میشود. درباره Arrayها باید یک سیاست مشخص وجود داشته باشد: ایندکس هر عضو در مسیر قرار گیرد، Array به همان شکل حفظ شود یا اعضا با الگویی جداگانه باز شوند.
- شروع با مسیر خالی و مقدار ریشه
- پیمایش تمام کلیدهای Object
- افزودن نام کلید به مسیر فعلی
- افزودن ایندکس برای اعضای Array طبق قرارداد
- ثبت مقدار نهایی در خروجی
- اعمال Escape برای کلیدهایی که شامل جداکننده هستند
شبهکد الگوریتم
function flatten(value, path, output):
if value is object:
for each key in value:
flatten(value[key], join(path, escape(key)), output)
else if value is array:
for each item, index in value:
flatten(item, join(path, index), output)
else:
output[path] = valueمثال ساده با Object تودرتو
{
"product": {
"title": "Keyboard",
"price": 120,
"stock": {
"warehouseA": 8,
"warehouseB": 3
}
}
}{
"product.title": "Keyboard",
"product.price": 120,
"product.stock.warehouseA": 8,
"product.stock.warehouseB": 3
}در این مثال، همه مقدارهای نهایی مسیر یکتا دارند. اگر این خروجی وارد جدول شود، هر مسیر میتواند یک ستون باشد. با این حال اگر محصولات ساختار متفاوتی داشته باشند، تعداد ستونها ممکن است بهسرعت زیاد شود و مدیریت داده دشوار گردد.
Flatten کردن Array در JSON
Array مهمترین بخش طراحی Flatten است، زیرا یک Array هم ترتیب دارد و هم میتواند اعضایی با ساختارهای متفاوت داشته باشد. رایجترین روش، استفاده از ایندکس عددی در مسیر است. در این حالت ترتیب اعضا حفظ میشود و Unflatten میتواند Array را بازسازی کند.
{
"order": {
"items": [
{"sku": "A1", "qty": 2},
{"sku": "B7", "qty": 1}
]
}
}{
"order.items.0.sku": "A1",
"order.items.0.qty": 2,
"order.items.1.sku": "B7",
"order.items.1.qty": 1
}برخی ابزارها از فرم براکتی مانند order.items[0].sku استفاده میکنند. هیچکدام ذاتاً استاندارد جهانی نیستند؛ مهم این است که Flatten و Unflatten از یک قرارداد مشترک استفاده کنند.
| روش نمایش Array | نمونه | مزیت | محدودیت |
|---|---|---|---|
| ایندکس نقطهای | items.0.name | ساده و سازگار با مسیر نقطهای | کلید عددی Object ممکن است مبهم شود |
| براکت | items[0].name | خوانایی بیشتر برای Array | Parser مسیر پیچیدهتر است |
| حفظ Array | items = [...] | خروجی کوتاهتر | ساختار کاملاً تخت نمیشود |
| Join کردن اعضا | tags = a,b | مناسب برخی CSVها | نوع و Escape ممکن است از بین برود |
مشکل کلیدهایی که نقطه یا جداکننده دارند
فرض کنید ورودی کلیدی با نام user.name داشته باشد. اگر نقطه جداکننده مسیر باشد، خروجی مشخص نمیکند این نام یک کلید واقعی است یا دو کلید user و name. برای جلوگیری از این ابهام باید یکی از راهها استفاده شود: Escape کردن نقطه، انتخاب جداکنندهای که در کلیدها وجود ندارد، نگهداری مسیر بهصورت آرایهای از قطعهها یا استفاده از قالبی که طول هر قطعه را مشخص میکند.
{
"user.name": "Ali",
"user": {"name": "Sara"}
}اگر هر دو مسیر بدون Escape به user.name تبدیل شوند، یک کلید روی دیگری نوشته میشود. این نمونه نشان میدهد Flatten صرفاً یک عملیات ظاهری نیست و قرارداد نامگذاری بخشی از صحت داده است.
رفتار با null، Object خالی و Array خالی
مقدار null یک مقدار واقعی JSON است و باید در خروجی ثبت شود. اما Object خالی و Array خالی هیچ مقدار نهایی ندارند؛ الگوریتم ساده بازگشتی ممکن است آنها را کاملاً حذف کند. در نتیجه پس از Unflatten دیگر مشخص نیست آن مسیر وجود داشته یا اصلاً در ورودی نبوده است.
- null را بهعنوان مقدار حفظ کنید.
- برای Object خالی یک نشانگر یا مقدار ویژه قابل تشخیص در نظر بگیرید.
- برای Array خالی نیز نوع Container را ثبت کنید.
- بین مسیر ناموجود و مسیر موجود با مقدار null تفاوت بگذارید.
- اگر بازگردانی لازم نیست، حذف Containerهای خالی را در مستندات خروجی اعلام کنید.
JSON Flatten در JavaScript
نمونه زیر یک پیادهسازی آموزشی است. این کد Object و Array را باز میکند و ایندکس Array را در مسیر قرار میدهد. برای محیط Production باید Escape کلیدها، تشخیص نوعهای غیر JSON و محدودیت عمق نیز اضافه شود.
function flattenJson(value, prefix = "", result = {}) {
if (Array.isArray(value)) {
if (value.length === 0) result[prefix] = [];
value.forEach((item, index) => {
const path = prefix ? `${prefix}.${index}` : String(index);
flattenJson(item, path, result);
});
return result;
}
if (value !== null && typeof value === "object") {
const keys = Object.keys(value);
if (keys.length === 0) result[prefix] = {};
keys.forEach((key) => {
const path = prefix ? `${prefix}.${key}` : key;
flattenJson(value[key], path, result);
});
return result;
}
result[prefix] = value;
return result;
}JSON Flatten در Python
def flatten_json(value, prefix="", result=None):
if result is None:
result = {}
if isinstance(value, dict):
if not value:
result[prefix] = {}
for key, item in value.items():
path = f"{prefix}.{key}" if prefix else str(key)
flatten_json(item, path, result)
elif isinstance(value, list):
if not value:
result[prefix] = []
for index, item in enumerate(value):
path = f"{prefix}.{index}" if prefix else str(index)
flatten_json(item, path, result)
else:
result[prefix] = value
return resultاین پیادهسازیها برای دادههای معمول مناسباند، اما کلید دارای نقطه را Escape نمیکنند. همچنین در Python نوعهایی مانند tuple یا Decimal جزو JSON استاندارد نیستند و باید پیش از Serialize شدن به نوع قابل نمایش در JSON تبدیل شوند.
کاربردهای واقعی JSON Flatten
تبدیل JSON به CSV
CSV ساختار ردیف و ستون دارد و Object تودرتو را مستقیماً نمایش نمیدهد. Flatten میتواند مسیرها را به نام ستون تبدیل کند. این روش زمانی بهترین نتیجه را دارد که همه رکوردها Schema نسبتاً ثابت داشته باشند. در دادههای بسیار ناهمگون، خروجی ممکن است صدها ستون پراکنده ایجاد کند.
گزارشگیری و داشبورد
ابزارهای گزارشگیری معمولاً با فیلدهای سطحی راحتتر کار میکنند. تبدیل مسیرهایی مانند customer.address.city به ستون مستقل، ساخت فیلتر، گروهبندی و محاسبه را ساده میکند.
مقایسه و Audit داده
در مقایسه دو JSON، نمایش تخت کمک میکند هر تغییر به یک مسیر مشخص نسبت داده شود. تغییر مقدار، حذف مسیر یا اضافه شدن مسیر بهصورت واضح دیده میشود. با این حال برای Arrayهایی که ترتیبشان مهم نیست، مقایسه صرفاً بر اساس ایندکس میتواند تغییرات کاذب زیادی ایجاد کند.
ذخیره در سیستمهای Key-Value
برخی Cacheها یا سیستمهای تنظیمات با کلیدهای مسیرمحور کار میکنند. Flatten میتواند هر مقدار را با یک کلید مستقل ذخیره کند. این روش برای بهروزرسانی جزئی مناسب است، ولی تراکنش و هماهنگی میان چند مسیر باید مدیریت شود.
JSON Flatten چه زمانی انتخاب مناسبی نیست؟
- وقتی ساختار درختی برای معنا و پردازش ضروری است.
- وقتی کلیدها بسیار پویا هستند و خروجی هزاران ستون ایجاد میکند.
- وقتی Arrayهای بزرگ دارید و تولید یک کلید برای هر عضو هزینه زیادی دارد.
- وقتی قرارداد Escape و Unflatten مشخص نیست.
- وقتی ابزار مقصد از JSON تودرتو پشتیبانی کامل دارد و Flatten فقط پیچیدگی اضافه میکند.
خطاهای رایج در Flatten کردن JSON
| خطا | پیامد | راهحل |
|---|---|---|
| نادیده گرفتن کلید دارای جداکننده | برخورد مسیر و از دست رفتن داده | Escape یا مسیر قطعهای |
| تبدیل همه مقادیر به String | از دست رفتن نوع Number و Boolean | حفظ نوع اصلی JSON |
| حذف Array یا Object خالی | بازسازی ناقص | ثبت Container خالی |
| فرض Object بودن کلید عددی | ابهام با ایندکس Array | ذخیره اطلاعات نوع مسیر |
| عمق نامحدود | خطر Stack Overflow یا مصرف زیاد | محدودیت عمق و ورودی |
| Overwrite بیصدا | از دست رفتن مقدار قبلی | تشخیص Collision و توقف با خطا |
نکات عملکرد برای JSONهای بزرگ
Flatten تعداد عناصر را کم نمیکند؛ فقط نحوه نمایش آنها را تغییر میدهد. در بسیاری از موارد، طول کلیدهای مسیر باعث افزایش اندازه خروجی میشود. برای دادههای بسیار بزرگ، ساخت یک Object کامل در حافظه ممکن است پرهزینه باشد. پردازش جریانی، ارسال زوجهای مسیر و مقدار بهصورت مرحلهای، محدودیت عمق و توقف زودهنگام میتواند مصرف حافظه را کنترل کند.
- قبل از پردازش، حداکثر اندازه و عمق ورودی را تعیین کنید.
- از Concatenation مکرر رشته در حلقههای بسیار بزرگ پرهیز کنید.
- اگر فقط چند مسیر لازم است، بهجای Flatten کامل همان مسیرها را استخراج کنید.
- برای Arrayهای عظیم، پردازش Streaming یا Chunk را در نظر بگیرید.
- خروجی را از نظر Collision بررسی کنید و Overwrite را مجاز نکنید.
نکات امنیتی
JSON Flatten رمزنگاری، پاکسازی یا اعتبارسنجی امنیتی نیست. داده حساس پس از Flatten همچنان حساس است و حتی ممکن است مسیرها اطلاعات بیشتری درباره ساختار سیستم افشا کنند. هنگام کار با داده کاربران، Tokenها، کلیدهای API یا اطلاعات هویتی، حداقلسازی داده و کنترل دسترسی ضروری است.
در زبانهایی مانند JavaScript باید از کلیدهای ویژهای که میتوانند رفتار Object را تغییر دهند مراقبت کرد. استفاده از Object با Prototype خنثی، Map یا بررسی نام کلیدها میتواند خطر Prototype Pollution را کاهش دهد. همچنین ورودی غیرقابل اعتماد نباید بدون محدودیت عمق یا حجم پردازش شود.
بهترین روشها برای JSON Flatten قابل بازگشت
- یک قرارداد ثابت برای جداکننده مسیر انتخاب کنید.
- Escape کلیدهای دارای جداکننده را تعریف کنید.
- شیوه نمایش Array و ایندکسها را مستند کنید.
- نوع مقدارها را بدون تبدیل ناخواسته حفظ کنید.
- Object و Array خالی را از دست ندهید.
- Collision مسیرها را خطا در نظر بگیرید.
- Flatten و Unflatten را با تست Round Trip بررسی کنید.
- برای ورودیهای بزرگ محدودیت اندازه و عمق بگذارید.
چکلیست قبل از استفاده
- آیا مقصد واقعاً به ساختار تخت نیاز دارد؟
- آیا خروجی باید دوباره Unflatten شود؟
- کلید دارای نقطه یا جداکننده چگونه Escape میشود؟
- Array با ایندکس نقطهای است یا براکت؟
- null و Container خالی حفظ میشوند؟
- نوع Number و Boolean دستنخورده میماند؟
- برخورد دو مسیر چگونه گزارش میشود؟
- محدودیت عمق و اندازه ورودی چیست؟
سؤالات متداول
آیا JSON Flatten اندازه فایل را کاهش میدهد؟
معمولاً نه. چون مسیر والد در چندین کلید تکرار میشود، خروجی تخت حتی میتواند بزرگتر شود. برای کاهش حجم باید از Minify، فشردهسازی انتقال یا طراحی داده مناسب استفاده کرد.
آیا Flatten و Minify یکساناند؟
خیر. Flatten ساختار تودرتو را به مسیرهای سطحی تبدیل میکند، اما Minify فقط فاصلهها و نویسههای غیرضروری نمایشی را حذف میکند و ساختار داده را تغییر نمیدهد.
آیا خروجی Flatten همیشه قابل Unflatten است؟
فقط زمانی که قرارداد مسیر بدون ابهام باشد، نوع Array و Object مشخص بماند، Containerهای خالی حذف نشوند و Collision رخ ندهد. در غیر این صورت چند ساختار متفاوت میتوانند یک خروجی مشابه تولید کنند.
برای Array بهتر است نقطه استفاده شود یا براکت؟
هر دو قابل استفادهاند. انتخاب باید با Parser مقصد و Unflatten هماهنگ باشد. براکت معمولاً Array را خواناتر نشان میدهد، ولی مسیر نقطهای پیادهسازی سادهتری دارد.
جمعبندی
JSON Flatten روشی کاربردی برای تبدیل دادههای تودرتو به مسیرهای کلیدمحور است و در تبدیل CSV، گزارشگیری، Diff، ایندکسگذاری و ذخیره Key-Value ارزش زیادی دارد. با این حال کیفیت نتیجه به قرارداد مسیر وابسته است. جداکننده، Escape، Array، null، Container خالی و Collision باید از ابتدا مشخص باشند. اگر هدف بازگردانی دقیق داده است، تست Round Trip میان Flatten و Unflatten ضروری است. برای انجام سریع این کار روی داده واقعی میتوانید از ابزار JSON Flatten سایت استفاده کنید و خروجی را پیش از استفاده در Pipeline بررسی کنید.