در این سری مطالب مرتبط با اینترنت اینها را گفتیم: دریافت اطلاعات از اینترنت، ارسال اطلاعات به اینترنت، نکات پیشرفته OkHttp و فرمت متداول و محبوب JSON برای تبادل اطلاعات.
در این مطلب میخواهیم نحوه کار کردن با JSON را ببینیم. برای تولید JSON از اشیاء جاوا و ساختن اشیاء جاوا از روی یک JSON کتابخانههای زیادی وجود دارد. یکی از معروفترین و کاربردی ترین آنها GSON است که توسط گوگل توسعه داده شده است.
در این مطلب با مثالهایی سعی میکنم نحوه کار GSON را در حدی که برای کارهای معمول به آن نیاز دارید به شما نشان بدهم.
GSON چیست و چگونه کار میکند؟
میتوانید پیچیدهترین اشیاء ساخته شده در زبانهای برنامهنویسی از چمله زبان جاوا را به فرمت JSON در بیاورید و در سمت دیگر در برنامههایی که به زبانهای مختلف نوشتهاند دوباره از روی آن JSON یک شیء بسازید. مثلاً ممکن است برنامه اندروید شما اطلاعاتش را به سروری بفرستد که با PHP یا C# با پایتون نوشته شده است. شما هیچ نگرانیای بابت فرمتهای مختلف اشیا در این زبانها نخواهید داشت، چون همه آنها فرمت استانداردی به نام JSON را میشناسند.
از آنجایی که ما در اسمارتلب به دنبال آموزش اندروید هستیم، به زبانهای دیگر غیر از جاوا کاری نداریم. حالا باید روشی پیدا کنیم که به سادگی یک شیء جاوا را بگیرد و JSON متناظر آن را بدهد و/با برعکس، یک JSON بگیرد و از روی آن یک شیء جاوا برای ما بسازد. برای این کار کتابخانههای زیادی وجود دارند که یکی از مهمترین و کاربردیترین آنها GSON است که گوگل نوشته و کدباز است و استفاده از آن بسیار ساده است و در حال حاضر بسیاری از برنامههای اندروید از آن استفاده میکنند.
نصب GSON
نصب GSON اگر از اندروید استودیو استفاده میکنید (یعنی هنوز هستند کسانی که استفاده نمیکنند؟) بسیار ساده است. کافی است خط زیر را بیلد گریدل برنامه خود اضافه کنید:
compile 'com.google.code.gson:gson:2.7'
(ممکن است در زمانی که شما این نوشته را میخوانید نسخههای جدیدتری منتشر شده باشند. اندروید استودیو موضوع را به شما یادآوری خواهد کرد.)
تبدیل کردن یک شیء جاوا به JSON
فرض کنید یک کلاس جاوا داریم به شکل زیر:
public class Person { private String name; private String family; public Person(String name, String family) { this.name = name; this.family = family; } // Getters and Setters... }
برای تبدیل کردن یک شیء ساخته شده از این کلاس به JSON با استفاده از کتابخانه GSON گوگل به شکل زیر عمل میکنیم:
Person person = new Person("Ali","Behzadian"); Gson gson = new Gson(); String personJson = gson.toJson(person);
تبدیل JSON به یک شیء جاوا با GSON
حالا فرض کنید میخواهیم از روی JSON ساخته شده قبلی یک شیء Person بسازیم:
// ... String personJson = gson.toJson(person); Person person2 = gson.fromJson(personJson, Person.class); // ...
همانطور که در مثالهای بالا دیدید، عمده کار این کتابخانه با دو متد toJson و fromJson انجام میشود.
GSON و اشیاء تو در تو
اشیاء تو در تو اشیائی هستند که یکی از ویژگیهای آنها خودش یک شیء ساخته شده از روی یک کلاس دیگر است. مثلاً فرض کنید به کلاس Person میخواهیم یک ویژگی دیگر به نام Contact اضافه کنیم که شامل آدرس، ایمیل و تلفن فرد باشد. برای این کار به دو کلاس جاوا نیاز داریم:
public class Contact { private String address; private String email; private String phone; public Contact(String address, String email, String phone) { this.address = address; this.email = email; this.phone = phone; } // Getters and Setters... } public class Person { private String name; private String family; private Contact contact; public Person(String name, String family, Contact contact) { this.name = name; this.family = family; this.contact = contact; } // Getters and Setters... }
اگر یک شیء از کلاس Person به شکل زیر داشته باشیم، JSON آن به چه شکلی خواهد بود:
Contact contact = new Contact("Tehran", "info@smartlab.ir", "02188776655"); Person person = new Person("Ali", "Behzadian", contact);
الان person یکی شیء تو در تو است. یعنی در داخل person یک شیء دیگر از نوع Contact وجود دارد. JSON این شیء به شکل زیر خواهد بود:
{ "name" : "Ali", "family" : "Behzadian", "contact" : { "address" : "Tehran", "email" : "info@smartlab.ir", "phone" : "02188776655" } }
همانطور که میبینید به عنوان مقدار contact یک شیء دیگر تعریف شده و تمام جفتهای نام/مقدار آن با مقدارهایی که برای شیء contact تعریف کردهایم یکی است.
تبدیل یک شیء تو در تو به JSON بسیار ساده است:
Gson gson = new Gson(); String personJson = gson.toJson(person);
شما نیازی نیست کار خاصی بکنید. خود GSON همه کارها را برای شما انجام خواهد داد.
حالا اگر بخواهیم همین JSON را دوباره به یک شیء جاوا تبدیل کنیم، باز هم کار بسیار ساده است:
// ... String personJson = gson.toJson(person); Person person2 = gson.fromJson(personJson, Person.class);
در واقع برای کار کردن با اشیاء تو در تو نیازی نیست کار بیشتری انجام دهیم.
GSON و لیستها و آرایهها
لیست و آرایه در جاوا دو ساختمان داده کاملاً متفاوت دارند. اما در JSON فقط یک شکل برای نمایش آرایه وجود دارد که در مطلب قبلی آن را نشان دادیم.
فرض کنید به کلاس Person یک آرایه یا یک لیست از یک شیء دیگر به نام Course اضافه کردهایم:
public class Course { private String name; private String code; public Course(String name, String code) { this.name = name; this.code = code; } // Getters and Setters } public class Contact { // ... } public class Person { private String name; private String family; private Contact contact; private List<Course> courses; // ... }
اگر دو نمونه از کلاس Course بسازیم و به شیء person اضافه کنیم، JSON آن به شکل زیر خواهد بود:
Contact contact = new Contact("Tehran", "info@smartlab.ir", "02188776655"); Person person = new Person("Ali", "Behzadian", contact); List<Course> courses = new ArrayList<Course>(); courses.add(new Course("Physics","1010")); courses.add(new Course("Mathematics","1020")); person.setCourses(courses); // ... String personJson = gson.toJson(person); Person person2 = gson.fromJson(personJson, Person.class);
JSON این شیء به شکل زیر خواهد بود:
{ "name" : "Ali", "family" : "Behzadian", "contact" : { "address" : "Tehran", "email" : "info@smartlab.ir", "phone" : "02188776655" }, "courses" : [ { "name" : "Physics", "code" : "1010" }, { "name" : "Mathematics", "code" : "1020" } ] }
برای تبدیل این شیء به JSON و/یا ساختن یکی شیء جاوا از روی JSON نیاز به هیچ کار بیشتری نیست:
// ... String personJson = gson.toJson(person); Person person2 = gson.fromJson(personJson, Person.class);
GSON و مقادیر null
اگر یکی از ویژگیهای یک شیء null باشد (مقداری نداشته باشد) GSON با آن چه میکند؟ فرض کنید یک شیء از روی کلاس Person میسازیم و به contact و courses آن مقداری نمیدهیم (یا صراحتاً آن را null مقداردهی میکنیم):
Person person = new Person("Ali", "Behzadian", null); person.setCourses(null); // ...
اگر با استفاده از متد toJson کتابخانه GSON این شیء را به JSON تبدیل کنیم، ماحصل کار شبیه JSON زیر خواهد شد:
{ "name" : "Ali", "family" : "Behzadian" }
چرا هیچ اثری از contact و courses در این JSON نیست؟ دلیل آن بسیار ساده است: GSON مقادیر null را کاملاً نادیده میگیرد. البته اگر بخواهید میتوانید این رفتار پیشفرض را تغییر دهید. برای این کار از کلاس GsonBuilder به شکل زیر استفاده میکنیم:
GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.serializeNulls(); Gson gson = gsonBuilder.create(); Person person = new Person("Ali", "Behzadian", null); person.setCourses(null); String personJson = gson.toJson(person);
حالا اگر این شیء را به JSON تبدیل کنیم، ماحصل این خواهد بود:
{ "name" : "Ali", "family" : "Behzadian", "contact" : null, "courses" : null, }
حاشیهنوشتهای GSON
یکی از امکانات GSON حاشیهنوشتها یا annotation های آن است. احتمالاً تا الان با یکی از حاشیهنوشتهای جاوا آشنا شدهاید: @Override. در این بخش چند حاشیهنوشت پر کاربرد GSON را با هم مرور می کنیم.
@Expose
فرایندی که یک JSON از روی یک شیء ساخته میشود serialization (سریالیزیشن) و زمانی که یک شیء جاوا از روی یک JSON ساخته میشود دیسریالیزیشن (deserialization) نامیده میشود. ممکن است بخواهیم یکی از ویژگیهای یک شیء جاوا در JSON تولیدی نیاید (یا اصطلاحاً serialize یا سریالایز نشود. درست مثل وقتی که مقدار آن null است. گاهی اوقات هم میخواهیم زمانی که از روی یک JSON یک شیء میسازیم (یا اصطلاحاً دیسریالیزیشن)، این ویژگی مقدارش را از JSON نگیرد (یا دیسریالایز نشود).
برای این کارها از حاشیهنوشت @Expose استفاده میکنیم. این حاشیهنوشت دو ویژگی دارد که کاملاً اختیاری هستند: serialize و deserialize. مقادیر قابل قبول برای ویژگیها مقادیر بولین (true و false) است.
به مثال زیر دقت کنید:
public class Person { // ۱ @Expose private String name; // ۲ @Expose(serialize = false, deserialize = false) private String family; // ۳ @Expose(serialize = false) private Contact contact; // ۴ @Expose(deserialize = false) private List<Course> courses; // ... }
۱- ویژگی name در این کلاس هم در زمان سریالایز کردن (ساختن یک JSON از روی شیء) وارد JSON میشود و هم در زمان دیسریالایز (ساختن شیء از روی JSON) مقدارش را از JSON میگیرد.
۲- ویژگی family در این کلاس نه در زمان سریالایز کردن (ساختن یک JSON از روی شیء) وارد JSON میشود و نه در زمان دیسریالایز (ساختن شیء از روی JSON) مقدارش را از JSON میگیرد.
۳- ویژگی contact در این کلاس زمان سریالایز کردن (ساختن یک JSON از روی شیء) وارد JSON نمیشود ولی در زمان دیسریالایز (ساختن شیء از روی JSON) مقدارش را از JSON میگیرد.
۴- ویژگی courses در این کلاس زمان سریالایز کردن (ساختن یک JSON از روی شیء) وارد JSON میشود ولی در زمان دیسریالایز (ساختن شیء از روی JSON) مقدارش را از JSON میگیرد.
اگر از روی کلاس بالا یک شیء بسازیم و سپس با GSON از روی آن شیء یک JSON بسازیم، شکل آنچگونه خواهد بود؟
{ "name" : "Ali", "courses" : [ { "name" : "Physics", "code" : "1010" }, { "name" : "Mathematics", "code" : "1020" } ] }
همانطور که در JSON تولید شده بالا میبینید، ویژگیهای family و contact به علت این که صراحتا گفتهایم:
@Expose(serialize = false) // OR @Expose(serialize = false, deserialize = false)
در این JSON حضور ندارند.
@SerializedName
بسیاری مواقع پیش میآید که نام یک ویژگی در JSON با نام همان ویژگی در کلاس جاوا یکی نیست. در این مواقع یک راه این است که نام ویژگی را یا در کلاس جاوا تغییر دهیم یا آن را در JSON تولید شده تغییر دهیم. ولی GSON برای این مواقع راهحل سادهتری برای ما تدارک دیده است: استفاده از حاشیهنوشت SerializedName. همانطور که در مثالها دیدید، کلاس Person یک ویژگی به نام name دارد. حالا اگر بخواهیم در زمان تولید JSON متناظر با این ویژگی یک جفت نام/مقدار با نام fullName داشته باشیم کافی است به شکل زیر عمل کنیم:
public class Person { @SerializedName("fullName") private String name; // ... }
حالا اگر یک JSON به شکل زیر داشته باشیم:
{ "fullName" : "Ali" }
مقدار fullName در ویژگی name کلاس وارد میشود.
قواعد نامگذاری
تا به حال با یکی از امکانات کلاس GsonBuilder آشنا شدهاید: serializeNulls. حالا میخواهیم یکی از کاربردیترین ویژگیهای این کلاس را با هم ببینیم: قواعد نامگذاری.
در زبانهای متفاوت معمولاً قواعد نامگذاری ویژگیهای کلاسها با هم متفاوت است. مثلاً در دات نت ویژگیها همگی با حرف بزرگ شروع میشوند. مثلاً ویژگی fullName جاوا در دات نت میشود FullName (به تفاوت حرف f دقت کنید). حالا اگر بخواهیم یک JSON که توسط برنامهای دات نتی تولید شده است را به یک شیء جاوا تبدیل کنیم، دو راه داریم:
۱- برای همه فیلدها از حاشیهنوشت SerializedName استفاده کنیم؛
۲- به GSON بگوییم از قواعد نامگذاری دات نت استفاده کند.
دومی قطعاً راه بسیار سادهتری است و جلوی بسیاری از خطاهای احتمالی را میگیرد.
برای این کار باید به شکلی به GSON بگوییم که از چه قاعده نامگذاریای استفاده کند. تعدادی از این قواعد در GSON از پیش موجودند ولی اگر بخواهید میتوانید قاعده نامگذاری خاص خودتان را بنویسید و از آن استفاده کنید (که موضوعی فراتر از این نوشته است).
تعدادی از کاربردیترین قواعد نامگذاری GSON اینها است:
IDENTITY
اگر از این قاعده نامگذاری استفاده کنیم، GSON نام ویژگیها را به هیچ وجه تغییر نمیدهد و نامها در JSON و در کلاس جاوا باید عیناً یکی باشند.
LOWER_CASE_WITH_UNDERSCORES
همانطور که از نام این قاعده معلوم است، این قاعده در زمان سریالایز کردن همه حروف را به حرف کوچک تبدیل میکند و بین کلمات از زیرخط (ـ) استفاده میکند. مثلاً اگر ویژگی کلاس به نام isDeveloper باشد در JSON متناظر به is_developer تغییر خواهد کرد.
LOWER_CASE_WITH_DASHES
در این حالت هم مثل حالت قبل همه حروف به حرف کوچک تبدیل میشوند ولی جداکننده کلمات حرف خط فاصله (دش) است. حالا اگر ویژگی کلاس به نام isDeveloper باشد در JSON متناظر به is-developer تغییر خواهد کرد.
UPPER_CAMEL_CASE
این قاعده نامگذاری همان قاعده نامگذاری در دات نت است. بسیار مشابه قاعده نامگذاری جاوا است با این تفادت که نام ویژگی هم با حرف بزرگ شروع میشود. مثلاً اگر ویژگی کلاس به نام isDeveloper باشد در JSON متناظر به IsDeveloper تغییر خواهد کرد.
UPPER_CAMEL_CASE_WITH_SPACES
در این قاعده نامگذاری، همه کلمات با فاصله خالی از هم جدا میشوند و هر کلمه با حرف بزرگ شروع میشود. مثلاً اگر ویژگی کلاس به نام isDeveloper باشد در JSON متناظر به Is Developer تغییر خواهد کرد.
برای استفاده از یک قاعده ناگذاری با استفاده از GSON باید به شکل زیر عمل کنید:
GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY); // OR gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); // OR gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES); // OR gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE); // OR gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES); Gson gson = gsonBuilder.create();
نکته: اگر برای یک ویژگی کلاس از حاشیهنوشت SerializedName استفاده کرده باشید، GSON به نام آن ویژگی دست نمیزند و آن را همانطور که در SerializedName تعریف کردهاید به کار میبرد، حتا اگر با قاعده نامگذاری تعریف شده سازگاری نداشته باشد.
با GSON کارهای بیشتری میتوان کرد ولی تا همین حد برای کار ما کافی است. اگر فکر کردید موضوع اینترنت و اندروید تمام شده است، سخت در اشتباهید! هنوز خیلی با این مبحث کار داریم!
سلام خسته نباشید بابت آموزش خیلی خیلی ممنونم میشه آموزش کار با والی رو هم قرار بدید؟؟
از اونجایی که والی (Volley) یک کتابخانه غیر رسمی و بدون مستندات است، از والی استفاده نمیکنم و همه کارهام رو با OkHttp و Retrofit و Glide انجام میدم. شاید در یک مطلب مستقل از والی هم نوشتم.
عجب!! همین چند جمله کافی بود که به اوج نبوغ جنابعالی پی ببریم! چشم شما با همون okHttp کار کنید استاد اعظم. قربون مشاوره هات جناب مشاور!
اون زمان که ایشون این مطلب رو نوشتن والی مستندات نداشته و پشتیبانی رسمی هم نمیشده، ولی الان که شما خطاب به ایشون اینطور نوشتید کتابخونه والی توسط خود گوگل مستندسازی شده. استفاده از کتابخونه ها هم سلیقه ای هست و شما هم اگر والی رو ترجیح میدین میتونید از منابع دیگه مطالعه کنید. من شخصا از آموزش های آقای بهزادیان خیلی استفاده کردم و اگر هم موضوع مدنظرم تو آموزش هاشون نبوده به منابع دیگه مراجعه کردم.
سلام
مطلب خلاصه و مفید بود.
خسه نباشی
سلام و خسته نباشید خدمت شما
خداقوت
خیلی خوب توضیح دادین.هیچ نکته مبهمی نموند.
خیلی عالی بود .کلی کمک کردین.بخصوص در مورد حاشیه نوشت ها
یا علی
باسلام
ممنون از سایت فوق العادتون
یه سوال:
من یه request دارم یه response
در request یه آی دی میفرستم سمت سرور و سرور اینو میگیره و یه خروجی بولین (true) پاسخ میدهد
من چطور این پاسخ true بگیرم و پردازشش کنم (مثلا true شد فعلا اکتیویتی رو اجرا کنه )
سمت سرور فقط به من true برمیگرونه (خروجی متدش این هست)
آیا سمت سرور مشکل دارد یا سمت اندروید؟
با تشکر
سلام. من کتابخونه هایی مثل gson رو که قبلا در android sdk نیستن نمیتونم با این روش compile در gradle.build به پروژه اضافه کنم.
خطا میده:
Error:Could not resolve all files for configuration ‘:app:debugCompileClasspath’.
Could not resolve com.google.code.gson:gson:2.8.2.
لازمه که قبلش VPN بزنید یا از فیلترشکن استفاده کنید. اگر از فیلترشکن استفاده میکنید، در اندروید استودیو پروکسی رو تنظیم کنید تا از فیلترشکن استفاده کنه.
سلام . در رابطه با مطلب Gson : اگر لیست یک کلاس داشته باشیم مثل List آنگاه از متد fromJason استفاده کنیم و نوع را هم به صورت Type collectionType = new com.google.gson.reflect.TypeToken<List>(){}.getType() تعریف کرده باشیم مشکل type mismatch میدهد. من دیتا را از یک سرویس wcf rest که با ویزوال استودیو نوشتم میگیرم. در دیباگ رشته ی json کاملا درست است . در واقع اول و آخر رشته با [ و ] شروع و خاتمه یافته و هر رکورد داخل آکولاد است.همانطور که باید باشد. هر دو کلاس در جاوا و ویزوال استوریو هم مثل هم است. این کلاس از سه فیلد ساده رشته ای تشکیل شده است. نکته این که وقتی پروژه روی یک ماشین دیگر مثل لپ تاپ اجرا می شود این تبدیل به شیی جاوا به خوبی انجام میشود.
سلام. خیلی ممنون از این آموزش.
یه سوال داشتم اینکه اگه از پاسخ json یک وب سرویس یک سری فیلدهاشو لازم داشته باشیم، و نخوایم همه این فیلدها رو توی کلاس جاوامون پیاده کنیم، یعنی فیلدهایی که لازم نداریم تو ساختار کلاس جاوامون نیاریم، باید چیکار کنیم؟ راه حلی داره که اون فیلدها از تو پاسخ json مون حذف شه؟ یا gson خودش بصورت خودکار فیلدهای متناظر جیسون که تو کلاس جاوا وجود نداشته باشند نادیده میگیره؟ ممنون.
کتابخانه GSON در موقع تبدیل json به java اگر مقداری در json باشد که ویژگی معادلی در جاوا نداشته باشد آنها را نادیده میگیرد.