پارس کردن JSON به صورت دستی و اتوماتیک در فلاتر

سلام دوستان گلم امیدوارم که حالتون خوب باشه و سال جدید رو با قدرت شروع کرده باشید.

اول از همه چیز بگم که از هم اکنون میتوانید ویدیوهای مربوط به آموزش برنامه نویسی مخصوصا فلاتر و دارت رو در کانال یوتوب من دنبال کنید. لینکشم میزارم همین پایین :

https://www.youtube.com/c/FlutterStan

تو این آموزش میخوایم پارس کردن Json رو فوتِ آب بشیم جوری که تا Json میزارن جلوتون سریع تو ذهنتون پارسش کنید ??. پس بزن بریم سر وقتش ??.

لازم به ذکره که پیش نیاز این مقاله آشنایی اندک با موارد زیر است :

زبان برنامه نویسی دارت

فریمورک فلاتر

مباحث ابتداییه کلاس ها و متدهای سازنده

مباحث اولیه کار با Mapها در دارت

خب اول از همه مثه تمام مقاله های دیگم از پایه و مفاهیم شروع میکنم :


اصلا این Json چی هست ؟

جی‌سان (JSON) مخفف کلمه JavaScript Object Notation بوده و یک استاندارد باز است که با ساختاری خوانا برای انسان و هم ماشین، می‌توان اطلاعات و داده‌های مختلف از جمله داده‌های یک دیتابیس را با استفاده از آن، بین عوامل مختلف مثلاً مرورگر کاربر و یک سایت منتقل کرد یا در فضای ذخیره سازی‌ای، آن را ذخیره نمود.

  • ساختار Json :

ساختار json بسیار ساده است و همین سادگی یکی از دلایل برتری آن نسبت به xml است چون با این ساختار، خود کاربر و انسان نیز می‌تواند به راحتی محتوا را بخواند.

قواعد کلی یک نوشته بصورت json به این شکل است:
محتوای داخل json با آکولاد باز ? } ? شروع شده و با آکولاد بسته ? { ? تمام می‌شوند. این بلاک به عنوان آبجکت مادر نیز شناخته می‌شود.
پس ینی تمام محتوای ما باید در یک { } قرار بگیرد.

  • آبجکت‌ها

شیء یا آبجکت (Object) در json شامل مجموعه‌ای نامرتب از داده‌ها (نام/مقدار => key / value) است که دارای یک نام رشته‌ای (داخل " ") به عنوان کلید است. کلید آبجکت‌ها بهتر است منحصر به فرد باشد تا به راحتی قابل تمایز باشند. آبجکت‌ها با آکولاد باز } شروع شده و با آکولاد بسته { تمام می‌شوند. کلید با کاراکتر دو نقطه : از آکولاد باز جدا می‌شود. داده‌های داخل آبجکت باید با کاراکتر کاما ( , ) از یکدیگر جدا شوند.

برای مثال:

{
    &quotPerson&quot: {
    &quotname&quot: &quotAli&quot,
    &quotlastName&quot: &quotHoseinpoor&quot,
    &quotbirth&quot: 1996
    }
}


در مثال بالا ما یک آبجکت با نام کلید Person داریم که دارای خصوصیاتی با مقادیر name برابر Ali و lastName برابر Hoseinpoor و birth برابر ۱۹۹۶ است.

همونجور که دیدید json با { } شروع شده و دارای یک آبجکت به اسم Person است که این آبجکت هم با { } شروع و خاتمه میابد.

  • آرایه‌ها

آرایه یا Array در json می‌تواند شامل چندین مقدار (از یک نوع ارزش) باشد. آرایه‌ها معمولاً دارای یک نام رشته‌ای (داخل " ") به عنوان کلید است. کلید آرایه‌ها بهتر است منحصر به فرد باشد تا به راحتی قابل تمایز باشند. آرایه‌ها با براکت باز ] شروع شده و با براکت بسته [ تمام می‌شوند. کلید با کاراکتر دو نقطه : از براکت باز جدا می‌شود. آبجکت‌های داخل آرایه باید با کاراکتر کاما ( , ) از یکدیگر جدا شوند.

برای مثال: آرایه‌ای از آبجکت‌ها

{
    &quotPersons&quot: [
    {&quotname&quot: &quotAli&quot, &quotlastName&quot: &quotHoseinpoor&quot},
    {&quotname&quot: &quotReza&quot, &quotlastName&quot: &quotHoseini&quot},
    {&quotname&quot: &quotNavid&quot, &quotlastName&quot: &quotHashemi&quot}
    ]
}


در این مثال ما یک آرایه به نام Persons داریم که دارای سه آبجکت است. هر آبجکت نیز دو جفت نام/مقدار دارد.
نمونه دیگر: آرایه‌ای از یک نوع مقادیر:

{
    &quotAges&quot: [
    ۲۵, ۱۲, ۶۵, ۱۶
    ]
}


نکته: آرایه فقط می‌تواند شامل یک نوع ارزش باشد. برای مثال یا همه آیتم‌هایش آبجکت باشد یا رشته یا ... .

اما این ارزش ها کلا چیا میتونن باشن ؟
•    رشته‌ها
•    اعداد
•    آبجکتی دیگر
•    آرایه‌ای دیگر
•    مقدار بولی - درست یا غلط (True / False)
•    مقدار تهی (Null)

قوانین JSON

  • عبارت‌های JSON باید میان آکولاد «{ }» قرار بگیرند.
  • اعضای شیء با علامت ویرگول«,» از هم جدا می‌شوند.
  • برای تعریف یک عضو در یک شیء JSON، ابتدا «نام عضو» سپس دونقطه«:» و در پایان «مقدار» نوشته می‌شود.
  • مقدار می‌تواند یکی از انواع «عدد»، «رشته»، «بولی»، «آرایه»، «شیء» و «نال» را بپذیرد. سایر انواع داده باید به صورتی دیگر ذخیره شوند.
  • انواع رشته‌ای باید داخل دو گیومه «"» قرار بگیرند.
  • بسته به زبان مبدا و مقصد انواع داده‌ی دیگری نیز ممکن است قابل پذیرش باشند.

خب به نظرم واسه مفاهیم کافی باشه بزن بریم سراغ پارس کردنش.

اولین و مهم ترین قانونی که توی پارس کردن وجود داره اینکه همیشه از درونی ترین آبجکت شروع کنید به پارس کردن.

قانون دوم : هر کلید/مقدار توی json باید یک فیلد متناظر در کلاس شما داشته باشه.

قانون سوم(تکمیلیه قانون بالا) : هر چیزی که از کلید/مقدارهای json مدنظرتون هست رو توی کلاس هاتون ذخیره کنید. به فرض شما نیاز ندارید تاریخ تولد رو توی کلاس هاتون داشته باشید پس اون رو نیاز نیست پارس کنید. مثلا اگه توی json ما یه آبجکت Person داشته باشیم که کلید های اسم، فامیل و تاریخ تولد رو داشته باشه و شما فقط به اسم و فامیل نیاز داشته باشید در این حالت نیاز نیس برای کلاسی که میخواید بسازید فیلد تاریخ تولد رو در نظر بگیرید.

قانون چهارم : هر آبجکت متناظر با یک کلاس در کدهاتون است. و هر آرایه متناظر با لیست.به فرض اگر شما یک آرایه از آبجکت توی json داشته باشید توی کدتون باید یک لیست از کلاس را نگه دارید. و یا اگر یک آرایه از رشته در json دارید در کدتون باید یک لیست از رشته را نگه دارید و ... .

خب الان با یک json خیلی ساده شروع میکنیم :

{
    &quotname&quot: &quotAli&quot,
    &quotlastName&quot: &quotHoseinpoor&quot,
    &quotbirth&quot: 1996
}

خب همونجوری که گفتم باید از درونی ترین آبجکت شروع کرد که تو این مثال اولیه ما کلا یه آبجکت بیشتر نداریم و همونجور که گفتیم هر آبجکت متناظر با یک کلاس هست. پس الان طرح اولیه کلاسمون رو مینویسیم :

class Person {
  final String name;
  final String lastName;
  final int birth;

  Person({
    this.name,
    this.lastName,
    this.birth,
  });
}

خب ما الان یه کلاس داریم به اسم Person(اسم کلاس به انتخاب خودتونه اما بهتره که اسامی مرتبط و قابل فهم باشه) که ۳تا فیلد تو خودش داره دقیقا متناظر با همون داده هایی که توی json بود. الان وقتشه یه متدی بنویسیم که کار پارس کردن رو واسه ما انجام بده :

factory Person.fromJson(Map<String, dynamic> json) {
  return Person(
    name: json['name'] as String,
    lastName: json['lastName'] as String,
    birth: json['birth'] as int,
  );
}

خب بریم ببینیم این کد اصن چی هست ؟ اصن factory چی میگه این وسط؟

خب من اول از همه یک named constructor (متد سازنده با اسم) ساختم به اسم Person.fromJson ینی در واقعا این همون متد سازنده هست و همونجور که میدونید توی دارت میشه چندتا سازنده داشت و اسمای مختلفی روشون گذاشت. پس چون این یک متد سازنده هست ما موظف هستیم که یک آبجکت از جنس Person رو برگردونیم. اما چرا factory ؟ بیایید یک نگاه به متد سازنده عادی داشته باشیم تا فرقش با factory رو بفهمیم :

Person() {
  name = 'Ali';
  lastName = 'Hoseinpoor';
  birth = 1996;
}

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

Person({String name, String lastName, int birth}) :
  name = name,
  lastName = lastName,
  birth = birth;

یا

Person({
  this.name,
  this.lastName,
  this.birth,
});

اگه کمی زبان دارت رو کار کرده باشید میدونید که در این دو سازنده مقدار دهی قبل از بدنه صورت میگیرد و دیگر ارور مربوط به final ها رو نداریم.

پس حالا فرق سازنده عادی با factory چی شد ؟ فرقش این شد که ما در سازنده عادی همیشه یک نمونه جدید از کلاس رو برمیگردونیم و دسترسی به کلمه return نداریم اما در factory ما میتونیم روی نمونه‌ای که میخوایم از کلاسمون برگردونیم کنترل داشته باشیم و به return هم دسترسی داریم. (از factory میتونیم توی singleton کلاس ها هم استفاده کنید. کلا چیز خوبیه یه سرچ دربارش حتما بکنید چون توضیح کاملش خارج از مبحث ما هست ) . مثلا فرض کنید قراره روی تاریخ تولد اول یه پردازشی انجام بشه بعد نتیجه اون پردازش بره بشینه توی پراپرتی birth.این factory به ما این قابلیت رو میده که بتونیم یک سری پردازش و ... رو انجام بدیم و در نهایت حتما یک نمونه از کلاس رو return کنیم.

پس اگه قرار متغیرهامون final باشن متد سازنده fromJson هم باید factory بشه چون ما قراره توی بدنه سازنده fromJson مقادیر json رو به پراپرتی ها نسبت بدیم و چون تو بدنه داریم این کارو میکنیم و متغیر ها هم final هستن اگه از factory استفاده نکنیم ارور میده که تمام متغیرهای final باید قبل از بدنه مقدار دهی بشن ولی اگه ما متد سازنده fromJson رو factory کنیم چون توش میایم یک نمونه از کلاس رو در آخر return میکنیم دیگه اون ارور مربوط به final ها رو نداریم.

کد کامل بدون استفاده از final :

class Person {
  String name;
  String lastName;
  int birth;

  Person({
    this.name,
    this.lastName,
    this.birth,
  });

  Person.fromJson(Map<String, dynamic> json) {
    name = json['Person']['name'] as String;
    lastName = json['Person']['lastName'] as String;
    birth = json['Person']['birth'] as int;
  }
}

کد کامل با استفاده از final :

class Person {
  final String name;
  final String lastName;
  final int birth;

  Person({
    this.name,
    this.lastName,
    this.birth,
  });

  factory Person.fromJson(Map<String, dynamic> json) {
   return Person(
      name: json['Person']['name'] as String,
      lastName: json['Person']['lastName'] as String,
      birth: json['Person']['birth'] as int,
    );
  }
}

فرقشون فقط توی استفاده از factory هست.

خب حالا که factory رو گفتم بریم سراغ ادامه تحلیل متد سازنده fromJson.

نکته بعدی ورودی هست که این متد قبول میکنه : Map<String,dynamic> json

شما وقتی json رو از سرور گرفتین باید به Map تبدیلش کنید که بتونید به راحتی ازش استفاده کنید و دیتاهاش رو در بیارین. کلید ها که همیشه رشته هستن ولی مقادیر میتونن انواع مختلف دیتا باشن واسه همین میگیم <String,dynamic>.

حالا که json رو به عنوان یک Map داریم میتونیم به راحتی داده هاش رو بیرون بکشیم و پراپرتی های کلاس رو مقدار دهی کنیم.

الان اگه بخوایم به مقدار name توی json (که الان به صورت Map در اختیار ماست) دسترسی داشته باشیم کافیه بگیم :

json['name']

ما برای دسترسی به مقادیر یک کلید در Map کافیه اسم کلید رو در [ ] بگیم. الان وقتی میگیم json['name'] ینی بیا

ما برای دسترسی به مقادیر یک کلید در Map کافیه اسم کلید رو در [ ] بگیم. الان وقتی میگیم json['name'] ینی بیا مقدار کلید name رو به من بده که در این حالت اسم رو به شما برمیگردونه. واسه بقیه پراپرتی ها هم همینطور.

پس نکته مهم این بود که json ما تبدیل بشه به Map (که اینم زمانی که دارین از سرور دیتا میگیرین باید اون Json رو دیکود کنین تا به شکل Map در بیاد که خارج از این آموزش هست.)و بعد با استفاده از کلیدهای Map بیایم و مقادیر رو بگیریم.

پس تا اینجا یاد گرفتیم که چجور داده های موجود در json رو واکشی کنیم و اونارو بریزیم توی فیلدهای کلاسمون.

پس الان اگه ما Person.formJson(json) رو صدا بزینم به ما یک کلاس برمیگردونه که پراپرتی هاش با مقادیر json مقدار دهی شده اند.

خب نظرت چیه سخت ترش کنیم json رو ؟

{
  &quotPerson&quot: {
  &quotname&quot: &quotAli&quot,
  &quotlastName&quot: &quotHoseinpoor&quot,
  &quotbirth&quot: 1996
  },
  &quotCar&quot: {
  &quotname&quot: &quotLamborghini&quot,
  &quotspeed&quot: 350
  }
}

خب الان json ما دوتا آبجکت داره یکی Person و یکی هم Car الان درونی ترین‌ آبجکت کدوم میشه ؟

فرقی ندارن جفت Person و Car در یک سطحن پس با هر کدوم میتونید شروع کنید.

طبق قانونی که گفتم هر آبجکت باید یک کلاس بشه پس بیایید کلاس هاشون رو بسازیم.

کلاس Car :

class Car {
  String name;
  String speed;

  Car({
    this.name,
    this.speed,
  });

  factory Car.fromJson(Map<String, dynamic> json) {
    return Car(
      name: json['name'] as String,
      speed: json['speed'] as String,
    );
  }
}

که دقیقا مثه قبل ساخته شده و چیز جدید و عجیبی نداره.

کلاس person هم که قبلا ساختیم :

class Person {
  final String name;
  final String lastName;
  final int birth;

  Person({
    this.name,
    this.lastName,
    this.birth,
  });

  factory Person.fromJson(Map<String, dynamic> json) {
   return Person(
      name: json['name'] as String,
      lastName: json['lastName'] as String,
      birth: json['birth'] as int,
    );
  }
}

خب حالا اگه به خود json دقت کنید میبینید که json همیشه خودش با یه آبجکت کلی(آبجکت مادر) شروع میشه(json همیشه با } شروع و با { تموم میشه) ینی در واقع ما یه آبجکت کلی داریم که توش دوتا آبجکت Person و Car هستش. پس از اونجایی که گفتم هر آبجکت باید یه کلاس باشه الان ما باید یه کلاس بسازیم که شامل دوتا کلاس Person و Car بشه.(واسه همین میگم همیشه از آبجکت ها درونی شروع کنید.)

class MyData {
  final Person person;
  final Car car;

  MyData({
    this.person,
    this.car,
  });

  factory MyData.fromJson(Map<String, dynamic> json) {
    return MyData(
      car: Car.fromJson(json['Car'] as Map<String, dynamic>),
      person: Person.fromJson(json['Person'] as Map<String, dynamic>),
    );
  }
}

کلاس MyData الان کلاس کلی من هست که توش کلاس Person و Car وجود داره. و توی متد سازنده MyData.fromJson اوومدیم این دوتا کلاس رو مقدار دهی کردیم اما چجور ؟

بیینید چون ما قبلا کلاس Person و Car رو ساخته بودیم و واسه هر کدومشون هم fromJson رو نوشته بودیم میتونیم اینجا برای مقدار دهی این دو کلاس از همون متد سازنده fromJson کلاس ها استفاده کنیم.

اگه دقت کرده باشید مثلا برای car گفتم : car: Car.fromJson(json['Car'] as Map<String, dynamic>)

به اون مقداری که به ورودی متد Car.fromJson دادم دقت کنید : json['Car']. ینی از اون json که داری مقدار کلید Car رو بده بهش تا بره اون مقدار رو پارس کنه و بریزه تو کلاس Car برای Person هم دقیقا همین عمل انجام شده.

خوب دوباره به json نگاه کنید بچه ها.(میخوام که قشنگ واستون جا بیوفته)
فایل json ما یه کلید داره به اسم Car که خودش دوباره یه آبجکته دیگس پس اگه من بگم json['Car'] در واقع مقدار کلید Car رو به من میده که مقدارش یه آبجکته حالا اگه این آبجکت که شامل name و speed هست رو بدم به Car.fromJson که قبلا نوشته بودم واسه کلاس Car میاد دقیقا اون آبجکت رو واسه من پارس میکنه و تهش یه نمونه از کلاس Car به من برمیگردونه که متغیرهاش با مقادیر json مقدار دهی شدن. این اتفاق دقیقا واسه Person هم میوفته

پس ما اول از درونی ترین آبجکت ها شروع کردیم و کلاس و fromJson اون ها رو ساختیم بعد یه لول اوومدیم بالاتر و یه کلاس دیگه ساختیم که شامل دوتا کلاس Person و Car باشه چون Json ما به این شکل بود یه آبجکت کلی بود که شامل دوتا آبجکت Person و Car بود پس ما هم باید یه کلاس داشته باشیم تا شامل دوتا کلاس Person و Car باشه.

الان هم میتونیم به راحتی بگیم : MyData.fromJson(json) تا واسه ما Json رو پارس کنه و یه کلاس به ما بده که شامل دیتاهای مد نظر ماست.

خب حالا یکم سخترترش کنیم ها ؟؟ ??

{
    &quotPerson&quot: {
    &quotname&quot: &quotAli&quot,
    &quotlastName&quot: &quotHoseinpoor&quot,
     &quotbirth&quot: {
        &quotyear&quot: 1996,
        &quotmonth&quot: &quotJan&quot,
        &quotday&quot: 26
       }
    },
    &quotCar&quot: {
    &quotname&quot: &quotLamborghini&quot,
    &quotspeed&quot: 350
    }
}

خب اگه دقت کنید این دفعه birth توی آبجکت Person از ۱۹۹۶ به یک آبجکت تغییر پیدا کرده که توی اون آبجکت سال و ماه و روز کامل نوشته شده.

پس الان درونی ترین آبجکت کدومه ؟؟ آفرین birth الان درونی ترین آبجکت میشه. پس اول واسه اون یک کلاس میسازیم :

class Birth {
  final int year;
  final String month;
  final int day;

  Birth({
    this.year,
    this.month,
    this.day,
  });

  factory Birth.fromJson(Map<String, dynamic> json) {
    return Birth(
      year: json['year'] as int,
      month: json['month'] as String,
      day: json['day'] as int,
    );
  }
}

این کلاس Birth یه نگا بهش بندازین. بازم هیچ چیز خاصی نداره و دقیقا مثه قبله.

خب حالا یه مرحله میریم بالا تر که میرسیم به آبجکت Person حالا واسه اون یه کلاس میسازیم :

class Person {
  final String name;
  final String lastName;
  final Birth birth;

  Person({
    this.name,
    this.lastName,
    this.birth,
  });

  factory Person.fromJson(Map<String, dynamic> json) {
    return Person(
      name: json['name'] as String,
      lastName: json['lastName'] as String,
      birth: Birth.fromJson(json['birth'] as Map<String, dynamic>),
    );
  }
}


تنها فرقی که کلاس Person کرده اینه که به جای int birth الان باید بشه Birth birth چون birth دیگه عدد نیست و الان یه آبجکته که کلاس شده و واسه پارس کردنش هم باید از متد سازنده fromJson کلاس Birth استفاده کنیم همونجور که نوشتیم : birth: Birth.fromJson(json['birth'] as Map<String, dynamic>)

پس کلاس Person هم به همین سادگی نوشته شد بریم سراغ Car.

کلاس Car هیچ تفاوتی نکرده و دقیقا مثه قبله پس دیگه نمینویسمش که شلوغ نشه اینجا.

و در نهایت یه مرحله میریم بالاتر و به آبجکت اصلی میرسیم که دوتا آبجکت Person و Car توش وجود داره. که اون کلاس هم دقیقا مثه قبله بدون هیچ تغییری.

بازم یکم سخت ترش کنیم ها ؟؟ میخوایم بریم سراغ آرایه ها

{
    &quotPerson&quot: {
        &quotname&quot: &quotAli&quot,
        &quotlastName&quot: &quotHoseinpoor&quot,
        &quotbirth&quot: {
            &quotyear&quot: 1996,
            &quotmonth&quot: &quotJan&quot,
            &quotday&quot: 26
        },
        &quotfamily&quot: [
            &quothasan&quot,
            &quotfarshad&quot,
            &quotrozhin&quot
        ]
    },
    &quotCars&quot: [
        {
            &quotname&quot: &quotLamborghini&quot,
            &quotspeed&quot: 350,
            &quotmojood&quot: false
        },
        {
            &quotname&quot: &quotBenz&quot,
            &quotspeed&quot: 300,
            &quotmojood&quot: true
        }
    ]
}

خب الان به آبجکت Person یه کلید family اضافه شده که یه آرایه از رشته هاس و کلید Car هم کلا تغییر کرده و شده Cars که یه آرایه از آبجکت هاس. پس بزن بریم.

دورنی ترین آبجکت چیه ؟دقت کن آبجکت ها فک نکنی آرایه هم جزو آبجکت حساب میشه. کلا آبجکت میشه هر چیزی که بین { } قرار بگیره(توی Json). پس درونی ترین آبجکت میشه همون birth که کلاسش دقیقا مثه قبله. یه مرحله میایم بالاتر و میرسیم به آبجکت Person که کلاس متناظرش میشه این :

class Person {
  final String name;
  final String lastName;
  final Birth birth;
  final List<String> family;

  Person({
    this.name,
    this.lastName,
    this.birth,
    this.family,
  });

  factory Person.fromJson(Map<String, dynamic> json) {
    return Person(
      name: json['name'] as String,
      lastName: json['lastName'] as String,
      birth: Birth.fromJson(json['birth'] as Map<String, dynamic>),
      family: json['family'] as List<String>,
    );
  }
}

همونجور که میبینید به این کلاس یه فیلد List<String> family اضافه شده که دقیقا متناظر با داده اون در json هست چون ما توی json یک آرایه از رشته ها داشتیم اینجا توی کلاس هم یک لیست از رشته ها ساختیم و اون رو توی fromJson مقدار دهی مکنیم دقیقا مثه قبل.

حالا میریم سراغ آبجکت هایی که توی آرایه Cars وجود داره. اونا هم کلاسشون به این صورت میشه :

class Car {
  final String name;
  final String speed;
  final bool mojood;
  

  Car({
    this.name,
    this.speed,
    this.mojood,
  });

  factory Car.fromJson(Map<String, dynamic> json) {
    return Car(
      name: json['name'] as String,
      speed: json['speed'] as String,
      mojood: json['mojood'] as bool,
    );
  }
}

خب حالا یه مرحله میایم بالاتر و میرسیم به آبجکت کلی که یه آبجکت Person توشه و یه آرایه از Car ها پس کلاس اون هم اینجوری میشه :

class MyData {
  final Person person;
  final List<Car> cars;

  MyData({
    this.person,
    this.cars,
  });

  factory MyData.fromJson(Map<String, dynamic> json) {
    final List<Car> carTemp = [];
    if (json['Cars'].length != 0) {
      json['Cars'].forEach((item) {
        carTemp.add(Car.fromJson(item as Map<String, dynamic>));
      });
    }
    return MyData(
      cars: carTemp,
      person: Person.fromJson(json['person'] as Map<String, dynamic>),
    );
  }
}

اینو یکم دقت کنید تا خوب متوجه بشید :

این json ما یه آبجکت Person داشت پس کلاس MyData ما هم باید توش یه نمونه از کلاس Person توش باشه دقیقا مثه قبل تا اینجا چیز خاصی نبود اما توی json , ما دیگه آبجکت Car نداریم بلکه یه آرایه از Car ها داریم پس توی کلاس MyData هم باید یه لیست از کلاس Car داشته باشیم . اما واسه پارس کردن این لیست از آبجکت Car ها ما باید اول یه لیست خالی بسازیم. بعد چک کنیم که آیا کلید Cars توی json مقدار داره یا نه یا به عبارت دیگه این آرایه از آبجکت های Car پر هست یا خالیه. حالا اگه خالی نبود با استفاده از forEach میتونیم روی دیتاهای Cars پیمایش کنیم و هر آبجکت Car رو با استفاده از Car.fromJson(json) پارس کنیم و به اون لیست اولیمون اضافه کنیم و در نهایت اون لیستی که اول ساختیم رو انتساب بدیم به پراپرتی cars در کلاس MyData دقیقا مثه کد بالا که نوشتیم. خوب بهش دقت کنید و سعی کنید همراه با خوندن کد متن من رو هم چند بار بخونید تا کامل متوجهش بشید.

شاید پیش خودتون بگید خب چرا با اون List<String> این کارو نکردی و از forEach و این چیزا استفاده نکردی؟
ببیند لیستی از رشته ها اصن نیاز به پارس شدن نداره همیشه لیستی از یه سری رشتس پس اگه من دیتاش رو بگیرم میتونم خیلی راحت اون لیست رو به فیلد توی کلاسم نسبتش بدم
اما لیستی از Car ها یه لیست از آبجکته که نیازه اون آبجکت ها هر کدوم جداگانه پارس بشن واسه همین از forEach استفاده کردم تا بتونم به تک تک آبجکت های توی Cars دسترسی داشته باشم و تک تک رو پارس کنم و به لیستی از کلاس Car اضافشون کنم.

خب تا اینجا سعی کردم مثال های مختلفی براتون بزنم و همه چیز رو بهتون بگم. بعدش که یکم دستتون راه افتاد واسه پارس کردن میتونید از پکیج فوقالعاده json_serializable واسه پارس کردن json هاتون استفاده کنید. که الان میخوام اونو هم بهتون یاد بدم.

خب در مرحله اول باید پکیج های json_serializable و json_annotation رو به لیست dependencies هاتون توی فایل pubsec.yaml اضافه کنید و همچنین باید پکیج build_runner رو به لیست dev_dependencies هاتون اضافه کنید. به شکل زیر :(فقط حواستون به فاصله ها توی pubspec باشه)

dependencies:
  flutter:
    sdk: flutter
   json_annotation: ^3.1.1
   json_serializable: ^3.5.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner:

اما کاری که این پکیج میکنه چیه ؟

ما دیگه نیاز نیس بیاییم دستی متد fromJson رو بنویسیم فقط کافیه یه کلاس بسازیم با پراپرتی هایی که اون کلاس نیاز داره و متد fromJson رو ایندفعه جوری که این پکیج میگه بنویسیم تا خود این پکیج واسه ما به صورت اتوماتیک کدش رو بنویسه اما چجور ؟؟

فرض کنید من این json رو دارم :

{
    &quotname&quot: &quotAli&quot,
    &quotlastName&quot: &quotHoseinpoor&quot,
    &quotbirth&quot: 1996
}

کافیه من یه کلاس بسازم و فیلدهای متناظر با json رو توش بزارم و اسم اون فیلد ها هم دقیقا مثه اسم همون کلید ها توی json بزارم(بعدا بهتون میگم چجور میشه اسم های متفاوت هم داشت) و به علاوه یکم کدهای دیگه که باید توی کلاسم بزارم. پس اول من کد رو میزارم و بعد ش توضیحش میدم :

part 'person_entity.g.dart';

@JsonSerializable(createToJson: false)
class PersonEntity {
  final String name;
  final String lastName;
  final int birth;

  PersonEntity({this.name, this.lastName, this.birth,});

  factory PersonEntity.fromJson(Map<String, dynamic> json) => _$PersonEntityFromJson(json);

}

خط اول چی میگه ؟

ببینید این پکیج با استفاده از پکیج build_runner واسه ما کد generate میکنه ینی میاد خودش یه فایل با پسوند g.dart میسازه و توش کد مینویسه و اون خط اول هم میگه که اون فایلی که قراره ساخته بشه بخشی از این فایل هست. اگه شما مثلا یه فایل ساختین به اسم person_entity.dart که توش کلاس PersonEntity رو نوشتین باید توی part اسم همون فایلتون رو بنویسین فقط پسوندش رو بکنید g.dart ینی person_entity.dart رو باید بنویسید person_entity.g.dart خب این از خط اول.

خط بعدی نوشته : @JsonSerializable(createToJson: false) این چی میگه ؟

ببینید ما با این خط داریم annotate میکنیم که این کلاس از JsonSerializable استفاده میکنه و توش هم گفتیم که createToJson برابر با false باشه ینی نمیخوام این پکیج برام متد toJson رو درست کنه فقط میخوام fromJson رو درست کنه(در ادامه این آپشنا رو کامل توضیح میدم)

بعدش اوومدم کلاسم رو به صورت عادی نوشتم و فیلدهاشو مشخص کردم و اسم فیلدها رو هم دقیقا مانند اسم کلید ها توی json گذاشتم. چون این پکیج به صورت اتوماتیک واسه واکشیه داده ها از json میاد از اسم خود فیلد های کلاس استفاده میکنه واسه همین باید اسم ها دقیقا مثه هم باشه(هر چند میشه اسم ها رو متفاوت گذاشت که بعدا بهتون میگم چجوری)

و در آخر متد fromJson رو نوشتیم. این خط کد دقیقا باید همینجوری باشه و نمیشه تغییرش داد فقط باید اسم کلاس هاتون رو جایگذاری کنید توش. مثلا اگه خواستم بعدا CarEntity داشته باشم جای factory PersonEntity.fromJson باید بنویسم factory CarEntity.fromJson و اون آخر هم جای _$PersonEntityFromJson(json) باید بنویسم _$CarEntityFromJson(json)

و تمام الان کافیه توی ترمینال این دستور رو وارد کنید تا خودش براتون کد رو بسازه :

flutter pub run build_runner build --delete-conflicting-outputs

بعد اینکه این دستور انجام شد میتونید ببینید که فایل person_entity.g.dart به پروژتون اضافه شده و میتونید بازش کنید وببینید که چجور واستون کد رو ساخته شما بعدش کافیه هر جا خواستین اون json رو پارس کنید بنویسید : PersonEntity.fromJson(json) همین.

حالا فک کنید من نمیخوام اسم پراپرتی های کلاسم با اسم کلید های json یکی باشه باید چکار کنم ؟

شما در این حالت میتونید از @JsonKey(name : 'test') استفاده کنید مثلا :

part 'person_entity.g.dart';

@JsonSerializable(createToJson: false)
class PersonEntity {
  final String name;
  
  @JsonKey(name: 'lastName')
  final String family;
  
  @JsonKey(name: 'birth')
  final int birthday;

  PersonEntity({this.name, this.family, this.birthday,});

  factory PersonEntity.fromJson(Map<String, dynamic> json) => _$PersonEntityFromJson(json);

}

اینجا با اینکه اسم کلید lastName بوده توی json ولی من تو کلاسم نوشتم family وبالاش با JsonKey(name:'lastName') گفتم که وقتی خواستی کد رو generate کنی به جای اینکه از اسم فیلد که family هست استفاده کنی بیا از اسم lastName واسه گرفتن دیتا از json استفاده کن. همینجور واسه birth هم همین کارو کردم و در نهایت باز اون دستور رو میزنم تا کد رو واسم generate کنه.

خب همین اول بگم که ما دوتا annotation داریم توی این پکیج
یکی JsonKey و یکی هم JsonSerializable
اولی که JsonKey باشه روی هر فیلد از کلاس تاثیر میزاره ینی شما باید واسه هر فیلد که خواستین، ازش جداگانه استفاده کنید. اینجوری نیس که یه سری قواعد کلی برای کلاستون و فیلدهاش مشخص کنه. تاثیرش فقط روی یدونه فیلده پس طبعا اگه بخواید روی چندتا فیلد تاثیر بزاره واسه هر فیلد جداگانه باید از JsonKey استفاده کنید. اما JsonSerializable تاثیر کلی داره ینی شما اونو ابتدای کلاس میزارید و میتونید واسه اون کلاس یه سری قواعد کلی رو تعیین کنید تا هنگام generate کردن کد واستون اون قواعد رو رعایت کنه این پکیج.

اما مقادیری که میتونیم به JsonKey بدیم چیا هستن ؟

  • name :

اینو گفتم که میتونیم ازش استفاده کنیم تا دقیقا بگیم که وقتی میخوای این فیلد رو مقدار دهی کنی از چه اسمی واسه واکشی داده از json استفاده کنی مثلا اگه همچین چیزی داشته باشم :

  @JsonKey(name: 'lastName')
  final String family;

بعد اینکه واسه من کد رو generate کرد همچین چیزی توی کد ساخته شده وجود داره :

family : json['lastName']

اگه هم که از name استفاده نکنم همچین چیزی واسه من درست میکنه :

family : json['family']

ینی به صورت دیفالت اسم خود فیلد رو میزاره واسه واکشیه داده ها از json.

پس با این name میشه دقیقا به این پکیج بگیم که از این اسم واسه گرفتن داده ها از اون json استفاده کن
در واقع ما داریم اسم اون کلید رو مشخص میکنیم چون همونجور که دیگه الان میدونید توی فایل json ما یه سری کلید داریم و وقتی هم این json به شکل Map توی کد ما در میاد ما باید اسم اون کلید رو به Map بدیم تا داده متناظر با اون کلید رو به ما بده که ما اگه واسش name انتخاب نکنیم به صورت دیفالت از اسم خود فیلدها استفاده میکنه حالا اگه اسم فیلد کلاسمون با اسم اون کلید در json متفاوت بود باید با name به این پکیج بگیم که آقا از اسم فیلد واسه گرفتن داده استفاده نکن بیا از این name استفاده کن به جاش

  • ignore :

وقتی ignore رو برابر با true میزاریم توی کد ساخته شده اون فیلد رو واسه ما مقدار دهی نمیکنه :

  @JsonKey(ignore: true)
  final String family;

الان وقتی میخواد فیلدهای کلاس رو با مقادیر json مقدار دهی کنه اون فیلدی که گفتیم ignore بشه رو کلا کاری باش نداره و ردش میکنه و اونو مقدار نمیده.
واسه جایی خوبه که شما خودتون بعدا میخواید اون فیلد کلاس رو پر کنید و اون فیلد ربطی به دیتاهای json نداره

  • defaultValue :

بعضی اوقات ممکنه اصلا کلیدی به اون نام که ما میخوایم از سمت سرور برنگرده ینی json ما اصلا کلید lastName رو نداشته باشه یا حتی داشته باشه ولی مقدار اون کلید null باشه ما با استفاده از defaultValue میتونیم توی این مواقع یه مقدار پیش فرض رو بدیم به فیلدمون مثلا :

@JsonKey(name: 'lastName',defaultValue: '')
  final String family;

این الان میگه که اولا از اسم lastName واسه پارس کردن استفاده کن بعدش اگه اصلا کلید lastName وجود نداشته یا مقدارش null بود بیاد به صورت دیفالت مقدارش رو یه رشته خالی بزار.


  • fromJson :

فرض کنید شما دارید تاریخ تولد رو از json میگیرید اما قبل از اینکه بخواید مقدار همون رو دقیقا بریزید تو فیلد کلاستون میخواید یه سری پردازش روش انجام بدید بعد اون مقدار نهایی رو به فیلد کلاستون انتساب بدید. تو این موقعیت fromJson به کارتون میاد. این fromJson یا باید یه global function (متد سراسری) باشه یا یه متد static مثلا :

part 'comment_entity.g.dart';

@JsonSerializable(createToJson: false)
class CommentEntity {
  final int id;
  final String text;
  @JsonKey(fromJson: _calculateDateTime)
  final String createdAt;

  CommentEntity({
    this.id,
    this.user,
    this.text,
  });

  factory CommentEntity.fromJson(Map<String, dynamic> json) => _$CommentEntityFromJson(json);

  static String _calculateDateTime(String dateTime) {
    return calculateDatetime(dateTime: dateTime);
  }
}

به کد بالا دقت کنید. من یه کلاس ساختم واسه پارس کردن comment ها که یه id دارن و یه text و یه createdAd که میشه تاریخ ساخته شدن اون کامنت. اما من نمیخوام که همون مقدار تاریخی که تو json هست رو توش بریزم مثلا میخوایم اگه تاریخش مال ۲ روز قبله اون تاریخ تبدیل بشه به 2days ago در واقع میخوام تاریخ رو تبدیل کنم به یه رشته که بگه مال چند روز قبله اون تاریخ. واسه همین از fromJson استفاده میکنم :

@JsonKey(fromJson: _calculateDateTime)
  final String createdAt;

که همونجور که گفتم این fromJson یا یه متد سراسری میگیره یا یه متد static که من اوومدم یه متد static به اسم _calculateDateTime نوشتم و این متد رو دادم به fromJson. حالا وقتی کد واسه من generate بشه هر موقع که خواست json رو پارس کنه به صورت اتوماتیک واسه پارس کردن createdAt میاد این متد رو صدا میزنه و مقدار خروجی از اون متد رو میریزه تو اون فیلد کلاس. فقط نکته مهم اینجاس که خروجی اون متد باید جنسش مشابه با اون فیلد تو کلاس باشه. ینی اگه فیلد createdAt از نوع String هست خروجی اون متد هم باید از نوع String باشه. و ورودی این متد هم باید جنسش دقیقا همون جنسی باشه که تو json برگشت داده میشه ینی اگه createdAt توی فایل json از نوع String هست ورودی متد هم باید String باشه.

پس خروجیه متد هم جنس با نوع فیلد توی کلاس و ورودیه متد هم هم جنس با نوع اون داده توی json.

در آخر اضافه کنم که این fromJson که اینجا هست هیچ ربطی به اون متد سازنده fromJson نداره. در واقع وقتی توی JsonKey از fromJson استفاده میکنیم که باید یه فانکشن حتما بهش پاس بدیم، این میاد میگه که اون فیلد از کلاسمون هر موقع خواست دیتا بگیره، بیا این متد رو براش صدا بزن و مقدار اون داده متناظر توی json رو بده به ورودیه این تابع تا اونو پردازش کنه و در نهایت اون تابع یه خروجی بده که اون خروجی در نهایت میره میشینه تو اون فیلد کلاسمون. پس به جای اینکه مستقیم دیتای json بره تو فیلد کلاسمون از یه فیلتر که یه تابع هست رد میشه و خروجیه اون تابع میره میشینه تو اون فیلد.


خب اینا ویژگی های مهم توی JsonKey بودن اما بریم سراغ ویژگی های مهم توی JsonSerializable :

  • createToJson :

اگه بگیم createToJson : false این به پکیج میگه که واسه من متد toJson رو نساز فقط همون fromJson رو بساز اگه هیچی هم نگیم به صورت دیفالت toJson رو هم میسازه واسمون.

@JsonSerializable(createToJson: false)


  • genericArgumentFactories :

این واسه کسایی که حرفه ای ترن به درد میخوره. در واقع میتونید از generic ها استفاده کنید. یه مثال میزنم(لطفا کسایی که با مبحث generic ها آشنایی دارن بخونن) :

part 'pagination_data_entity.g.dart';

@JsonSerializable(genericArgumentFactories: true, createToJson: false)
class PaginationDataEntity <T>{
  final List<T> items;
  final MetaPaginationEntity meta;

  PaginationDataEntity({
    this.items,
    this.meta,
  });

  factory PaginationDataEntity.fromJson(Map<String, dynamic> json,T Function(Object json) fromJsonT,) => _$PaginationDataEntityFromJson(json,fromJsonT);
}

من توی این کلاس میخوام لیستم generic باشه واسه همین باید genericArgumentFactories رو برابر با true کنم و از T هم استفاده کنم. فقط تنها فرقش اینه که توی متد fromJson الان دوتا ورودی میگیریم به جای یه ورودی. قبلا فقط json رو میگرفتیم الان یه فانکشن هم باید بهش پاس بدیم. و اما نحوه استفادش اینجوری میشه :

PaginationDataEntity<Test>.fromJson(
  json as Map<String, dynamic>,
  (data) => Test.fromJson(data as Map<String, dynamic>),
),

الان T من شده کلاس Test و توی متد سازنده fromJson کلاس PaginationDataEntity علاوه بر json یک فانکشن هم بهش دادیم که توش باید از fromJson اون کلاس T مون که الان کلاس Test هست استفاده کنیم.


اینم مهم ترین ویژگی های JsonSerializable حالا میریم یه مثال سخت باش حل میکنیم.

این json ما :

{
    &quotperson&quot: {
        &quotname&quot: &quotAli&quot,
        &quotlastName&quot: &quotHoseinpoor&quot,
        &quotbirth&quot: {
            &quotyear&quot: 1996,
            &quotmonth&quot: &quotJan&quot,
            &quotday&quot: 26
        },
        &quotfamily&quot: [
            &quothasan&quot,
            &quotfarshad&quot,
            &quotrozhin&quot
        ]
    },
    &quotcars&quot: [
        {
            &quotname&quot: &quotLamborghini&quot,
            &quotspeed&quot: 350,
            &quotmojod&quot: false
        },
        {
            &quotname&quot: &quotBenz&quot,
            &quotspeed&quot: 300,
            &quotmojod&quot: true
        }
    ]
}

خب من اول یه فایل به اسم my_data_entity.dart میسازم و کدهامو تو اون فایل میزنم(شما میتونید هر کلاس رو توی فایل های مجزا بزنید.)

خب میریم سراغ درونی ترین آبجکت تا کلاسش رو بسازیم. ینی آبجکت birth.

(دقت کنید همه کدها درون فایل my_data_entity.dart هستن)

import 'package:freezed_annotation/freezed_annotation.dart';

part 'my_data_entity.g.dart';

@JsonSerializable(createToJson: false)
class Birth {
  final int year;
  final String month;
  final int day;

  Birth({
    this.year,
    this.month,
    this.day,
  });

  factory Birth.fromJson(Map<String, dynamic> json) => _$BirthFromJson(json);
}

همونجور که دیدید اولش از part استفاده کردم تا واسه من کدی رو که توی فایل my_data_entity.g.dart درست میشه رو بخشی از این فایل my_data_entity.dart بکنه و بعدش هم به صورت عادی کلاسم رو نوشتم ومتد fromJson رو جوری که خود پکیج گفته نوشتم. حالا بریم سراغ کلاس Person :

@JsonSerializable(createToJson: false)
class Person {
  final String name;
  final String lastName;
  final Birth birth;
  final List<String> family;

  Person({
    this.name,
    this.lastName,
    this.birth,
    this.family,
  });

  factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}

این هم کلاس Person که دقیقا نحوه ساختش مثه همون Birth هست و حواستون باشه توی کلاس Person ما یه فیلد از کلاس Birth رو داریم دقیقا مثه کدای قبل. چون توی فایل Json در آبجکت person ما آبجکت birth رو داریم پس توی کلاس هاشون هم این اتفاق میوفته ینی توی کلاس Person باید کلاس Birth رو داشته باشیم. حالا میریم سراغ کلاس Car:

@JsonSerializable(createToJson: false)
class Car {
  final String name;
  final int speed;
  final bool mojood;

  Car({
    this.name,
    this.speed,
    this.mojood,
  });

  factory Car.fromJson(Map<String, dynamic> json) => _$CarFromJson(json);
}

این هم کلاس Car که دقیقا مثه بقیه ساخته میشه. (فقط حواستون باشه که اسم کلاس توی متد fromJson رو عوض کنید)

خب حالا باید بریم سراغ آبجکت اصلیمون که شامل آبجکت Person و لیستی از آبجکت Car هاست. و باید این آبجکت اصلی رو به کلاس تبدیل کنیم پس داریم :

@JsonSerializable(createToJson: false)
class MyData {
  final Person person;
  final List<Car> cars;

  MyData({
    this.person,
    this.cars,
  });

  factory MyData.fromJson(Map<String, dynamic> json) => _$MyDataFromJson(json);
}

این هم کلاس اصلیمون به همین راحتی. حالا فقط کافیه اون دستور رو اجرا کنیم و صبر کنیم تا واسمون کد رو generate کنه.

بعدش که کد با موفقیت generate شد میتونید برید و کد رو خودتون نگا کنید و ببینید که اگه خودتون میخواستین اون کدرو بزنید چقد زیاد و وقت گیر میشد. اینجوری فقط نیازه شما بدنه اصلی کلاس رو بنویسید و اون متد fromJsonرو جوری که خود پکیج گفته(و ما هم اینجا دقیقا همونجوری نوشتیم) رو در کلاستون بنویسید و همچنین part رو هم فراموش نکنید.

خب دوستان اینم از آموزش پارس کردن json هم به صورت دستی هم با استفاده از پکیج.
امیدوارم که آموزش مفیدی بوده باشه براتون و تونسته باشم چیزی هر چند اندک به اندوخته هاتون اضافه کنم.
تا آموزشای بعدی خدانگهدار❤️❤️.


لینک کانال یوتوب من لطفا سر بزنید : https://www.youtube.com/c/FlutterStan