یک مستطیل و یک درخت
CSV یک مستطیل است. هر رکورد یک سطر است، هر جداکننده به ستون بعدی میرود، و سطر اولِ اختیاری به آن موقعیتها نام میدهد. یک فیلد وقتی درون نقلقول باشد میتواند جداکننده یا شکست خط داشته باشد، اما ساختار مسطح میماند: سطر، ستون، خانه.
JSON یک درخت است. دو ظرف آن معناهای متفاوتی دارند:
- شیء نامها را به مقادیر نگاشت میکند.
- آرایه مقادیر را به ترتیب نگه میدارد.
هر یک میتواند شیء یا آرایهٔ دیگری در خود داشته باشد، و هیچ قاعدهای نمیگوید دو شیء کنار هم باید نامهای یکسانی داشته باشند. همین انعطاف است که JSON را برای پاسخ API مناسب میکند و همین است که «JSON را به CSV تبدیل کن» را دستوری ناکامل میکند. مبدل باید انتخاب کند درخت را کجا به سطرها ببُرد و مسیرهایی را که به هر برگ میرسند چگونه نامگذاری کند.
سه شکلی که صادقانه جدولیاند
سه شکل رایج در سطح بالا هست که قاعدهٔ سطرِ قابل دفاعی دارند.
یک شیء
{ "name": "Ada", "city": "London" }
این میتواند یک سطر با دو ستون باشد. جدولی کوچک است، اما همچنان جدول است.
آرایهای از شیءها
[
{ "name": "Ada", "city": "London" },
{ "name": "Grace", "city": "New York", "language": "COBOL" }
]
هر شیء یک سطر است و اجتماع کلیدها سرآیند است: name، city و سپس language. خانهٔ زبان برای Ada خالی است. گرفتن عنوانها فقط از شیء اول، فیلد Grace را بیسروصدا دور میریخت، و به همین دلیل تبدیل درست باید پیش از نوشتن سرآیند هر رکورد را بررسی کند.
آرایهای از آرایهها
[
["Ada", "London"],
["Grace", "New York"]
]
هر آرایهٔ درونی یک سطر است و موقعیت همان ستون. اینکه موقعیت صفر چه معنایی دارد غایب است، پس مبدل عمومی میتواند آن را column_1 بنامد اما نمیتواند صادقانه عنوان name را از خودش بسازد. آرایهها فشردهاند و وقتی طرحواره جداگانه همراه میشود مفیدند. وقتی کسی فقط فایل را در دست دارد شکنندهاند.
پیش از ادامهٔ مطلب
اولین شیء JSON کلیدهای name و city دارد. صدمین شیء language هم دارد. کی میتوان سرآیند CSV را با اطمینان نوشت؟
پس از آنکه مجموعهٔ کامل رکوردها بررسی شده باشد. یک شیء JSON مقید به کلیدهای شیء قبل از خودش نیست، و دادهٔ تُنُک عادی است: یک مشتری نام میانی دارد، یک فاکتور شمارهٔ مالیاتی دارد، یک نتیجهٔ API هشداری اختیاری همراه دارد. اگر شیء اول بهتنهایی سرآیند را تعریف کند، فیلدهای بعدی ناپدید میشوند و خروجی همچنان کاملاً معتبر به نظر میرسد. سرآیند امن اجتماع کلیدهاست، به ترتیب اولین مشاهده، تا نتیجهاش قطعی و خوانا باشد. رکوردهایی که یکی از آن کلیدها را ندارند زیر آن عنوان خانهای خالی میگیرند.
چرا شیءها معمولاً سطر JSON امنتری هستند
یک رکورد را پس از افزوده شدن یک فیلد مقایسه کنید.
// positional
["Ada", "Lovelace", "London"]
// named
{ "first": "Ada", "last": "Lovelace", "city": "London" }
حالا یک نام میانی درج کنید. در آرایه، هر مصرفکننده باید بداند که شهر از موقعیت دو به موقعیت سه رفته است. یک مصرفکنندهٔ قدیمیتر میتواند "Byron" را بدون هیچ خطایی به عنوان شهر بخواند. در شیء، افزودن "middle": "Byron" معنای city را تغییر نمیدهد و مصرفکنندهای که به فیلد جدید نیاز ندارد میتواند نادیدهاش بگیرد.
آرایهها در تکرار برندهاند: هزاران سطر همان نامهای کلید را تکرار نمیکنند. این صرفهجویی درون یک پروتکل پرحجم با طرحوارهٔ نسخهبندیشده اهمیت دارد. در یک فایل ۲۰ مگابایتی که به یک آدم داده میشود معمولاً اهمیتی ندارد، جایی که توانایی خواندن و عیبیابی داده بیش از چند کیلوبایت فشردهشده میارزد.
به همین دلیل CSV به JSON بهطور منطقی پیشفرضش یک شیء به ازای هر سطرِ سرآینددار است و آرایهها را به عنوان گزینهای صریح پیشنهاد میدهد. سرآیند از قبل نامها را داده است؛ دور ریختن آنها باید یک انتخاب باشد، نه اثر جانبی.
حالت عکس هم قابل توجه است. جدول نسبت از همان ابتدا یک مستطیل است: هر ضریب یک سطر است و دو کمیت مقیاسشده ستونهای پایدارند. میتواند بدون ساختن طرحواره به CSV تبدیل شود، چون خودِ محاسبه از قبل تعیین کرده هر سطر و ستون یعنی چه.
تودرتویی جایی است که مدل نمایان میشود
یک مقدار را در نظر بگیرید:
{
"name": "Ada",
"address": {
"city": "London",
"postcode": "SW1A"
},
"roles": ["mathematician", "writer"]
}
مستطیل خانهٔ تودرتو ندارد. دو پاسخ رایج هر دو مشروعاند و همارز نیستند.
مقدار تودرتو را سریالسازی کنید. خانهٔ address شامل JSON فشرده و خانهٔ roles شامل آرایهٔ JSON است. مقادیر قابل بازیابی میمانند، اما صفحهگسترده نمیتواند بدون تجزیهٔ دوبارهٔ خانه، مستقیماً بر اساس شهر مرتب کند.
مسیرهای شیء را مسطح کنید. عنوانها میشوند address.city و address.postcode. این برای فیلتر کردن راحت است و نام والد را در هر عنوان حفظ میکند. آرایهها معمولاً باید یک خانهٔ JSON بمانند: باز کردن roles[0]، roles[1] و بعدیها باعث میشود طول فهرست طرحواره را دیکته کند و به این معناست که اولین عضو در هر سطر معنایی پایدار دارد.
هیچیک از دو پاسخ را نمیتوان بهطور عام درست استنباط کرد. JSON به CSV این انتخاب را نمایان میکند، چون پنهان کردنش پشت «خودکار» یکی از کاربردها را بیسروصدا اشتباه میکرد.
نوعها در مرز CSV ناپدید میشوند
این مقادیر JSON از هم متمایزند:
{ "number": 42, "word": "42", "nothing": null, "missing": "not present at all" }
CSV فقط نویسههای میان مرزها را دارد. هر دو مقدار ۴۲ به همان دو نویسهٔ 42 تبدیل میشوند؛ null و فیلد غایب، هر دو زیر قرارداد معمول، به خانهٔ خالی تبدیل میشوند. صفحهگسترده ممکن است هنگام باز کردن فایل عدد، تاریخ یا فرمولی استنباط کند، اما آن استنباط رفتار واردکننده است، نه اطلاعات نوعی که CSV حمل کرده باشد.
این، محافظت در برابر فرمول را هم توضیح میدهد. رشتهای در JSON که با =، +، - یا @ شروع میشود صراحتاً متن است. CSV نمیتواند این را بگوید، و نرمافزار صفحهگسترده ممکن است آن را عبارت تفسیر کند. گذاشتن یک آپاستروف در ابتدای فیلد، قصد منبع — متن — را حفظ میکند، در حالی که یک عدد واقعی JSON مانند -2 بدون پیشوند به صورت متن عددی میماند.
قالببندی JSON، تبدیل مدل آن نیست
زیبا نوشتن آکولادها و تورفتگیها کمپیامدترین مرحلهٔ این گردش کار به نظر میرسد، اما میانبر معمول تجزیهورشتهسازی (parse and stringify) از همان مرز مدلی میگذرد که یک مبدل میگذرد. وقتی یک عدد JSON به عدد binary64 زبان تبدیل شود، عدد صحیحی فراتر از بازهٔ دقیق میتواند رقمهای متفاوتی پیدا کند. وقتی شیئی با نامهای تکراری به نگاشت عادی برنامه تبدیل شود، یکی از آن اعضا میتواند ناپدید شود. آنگاه سریالساز مقادیر خودش را مینویسد، نه اینکه صرفاً منبع را بچیند.
یک قالببند حافظ توکن مسیر باریکتری میرود: کل سند را اعتبارسنجی میکند، رشتههای نقلقولشده و گریزها را دنبال میکند، و فقط چهار نویسهٔ فضای سفیدی را تغییر میدهد که دستور زبان JSON میان توکنها مجاز میداند. این کار فایل خوانا را خوانا نگه میدارد بیآنکه تصمیم بگیرد ترتیب کلیدها، نامهای تکراری یا شیوهٔ نوشتن نمای اعداد اتفاقی بودهاند. وقتی مدل باید تغییر کند از تبدیل مدل استفاده کنید؛ وقتی چیدمان تنها تغییر خواستهشده است از قالببند.
رفتوبرگشت تصمیمها را حفظ میکند، نه بایتها را
فرض کنید یک CSV سرآینددار به آرایهای از شیءهای JSON و سپس دوباره به CSV تبدیل شود. جدول ممکن است همچنان همان مقادیر را داشته باشد، اما یکسانی بایتها انتظار معقولی نیست:
- نقلقولها ممکن است اضافه یا حذف شوند و همچنان همارز بمانند.
- پایانخطها ممکن است به CRLF نرمال شوند.
- ورودی با نقطهویرگول یا تب ممکن است به صورت خروجی کاماجدا برگردد.
- مرحلهٔ JSON نمیتواند حفظ کند که آیا
42عمداً متن بوده است. - null، فیلد غایب و رشتهٔ خالی ممکن است همگی در یک خانهٔ خالی به هم برسند.
- ممکن است نشانهٔ UTF-8 برای سازگاری با Excel اضافه شود.
وقتی قرار است دو فایل یکسان باشند از چکسام استفاده کنید. وقتی یک مدل داده عمداً از مدل دیگری عبور کرده، از آزمون ساختاری — همان عنوانها، سطرها و قراردادهای اعلامشدهٔ مقادیر — استفاده کنید.
قاعدهٔ عملی
وقتی آدمها فایل را بازرسی میکنند، وقتی رکوردها ممکن است تُنُک باشند، یا وقتی طرحواره قرار است تکامل یابد، از آرایهای از شیءها استفاده کنید. وقتی موقعیت از پیش قراردادی رسمی است و انتقال فشرده اهمیت دارد، از آرایهای از آرایهها. وقتی رفتوبرگشت مهم است مقادیر تودرتو را به صورت JSON نگه دارید؛ وقتی تحلیل در صفحهگسترده مهم است مسیرهای شیء را مسطح کنید.
از همه مهمتر، این انتخابها را کنار خروجی بنویسید. تبدیل صرفاً جایگزین کردن آکولاد با کاما نیست. لحظهای است که درخت به مستطیل تبدیل میشود، و تصمیمهایی که آنجا گرفته میشوند تعیین میکنند که آیا سطرهای بعدی هنوز همان معنایی را دارند که منبع داشت.
پرسشهای رایج
آیا CSV میتواند اعداد، بولیها و null در JSON را دقیقاً حفظ کند؟
نه. فیلدهای CSV متناند و هیچ اعلان نوع استانداردی ندارند. متن ۴۲ ممکن است بعداً عدد استنباط شود، «true» ممکن است در یک واردکننده بولی شود و در دیگری یک واژه، و یک خانهٔ خالی نمیتواند بدون قراردادی توافقشده بیرون از CSV، فیلد غایب را از null در JSON تشخیص دهد.
آیا هر آرایهٔ JSON یک جدول است؟
نه. آرایه میتواند مقادیر اولیه، مقادیر مخلوط، فهرستهای تودرتو با طولهای بیربط یا شیءهایی بدون معنای مشترک داشته باشد. فقط وقتی جدولی است که هر عضو یک نوع سطر واحد را نمایندگی کند و راهی پایدار برای نسبت دادن هر مقدار به یک ستون وجود داشته باشد.
چرا دو رکورد JSON میتوانند کلیدهای متفاوتی داشته باشند؟
شیءهای JSON نگاشتهای مستقلاند، نه سطرهایی که یک طرحواره مقیدشان کرده باشد. پاسخهای تُنُک API اغلب مقادیری را که در دسترس نیستند حذف میکنند و رکوردهای بعدی ممکن است فیلدهایی بیاورند که رکورد اول نداشت، پس مبدل باید مجموعهٔ کامل را بررسی کند نه اینکه شیء اول را قانون بداند.
آیا CSV به JSON و دوباره به CSV، بایتهای اصلی را بازتولید میکند؟
معمولاً نه. CSVهای همارز میتوانند نقلقولگذاری، جداکننده، پایانخط یا ترتیب ستون متفاوتی داشته باشند، و تبدیل به JSON تمایزهایی مانند اینکه آیا ۴۲ بدون نقلقول به معنای متن بوده یا عدد را از دست میدهد. رفتوبرگشت میتواند مقادیر جدول را زیر قواعد اعلامشده حفظ کند بیآنکه فایل اصلی را بایت به بایت حفظ کند.
آیا قالببندی JSON مدل دادهٔ آن را تغییر میدهد؟
قالببندی که فقط با فضای سفید سروکار دارد نیازی به این کار ندارد. میتواند JSON را اعتبارسنجی کند و ترتیب کلیدها، نامهای تکراری، شیوهٔ نوشتن اعداد و گریزهای رشته را حفظ کند و فقط فضای سفید بیاهمیت را تغییر دهد. تجزیه به مقادیر برنامه و سریالسازی دوباره عملیاتی گستردهتر است که میتواند برخی از آن تمایزهای سطح منبع را نرمال کند یا دور بریزد.