ว่าด้วยเรื่อง @SerializedName ใน Gson และ ProGuard

ถ้านึกจะเขียนแอปฯที่เอาไว้เรียกข้อมูลจาก Web Service ก็คงไม่พ้น Retrofit ยอดนิยมที่คอมโบคู่กับ Gson เพื่อแปลงข้อมูลจาก JSON ให้กลายเป็น Object (Model Class) และในบทความนี้ก็จะมาพูดถึง @SerializedName ของ Gson เมื่อต้องใช้ ProGuard กันครับ

เจ้าของบล็อกก็เป็นคนหนึ่งที่ประทับใจใน @SerializedName มากๆ เพราะว่ามันแก้ปัญหาเรื่อง Key ใน JSON ไม่ตรงกับชื่อของตัวแปรใน Object เพราะว่าวิธีการตั้งชื่อของทั้งสองอย่างนั้นไม่เหมือนกันหรืออยากจะเปลี่ยนเป็นชื่ออื่นเลยก็ทำได้เช่นกัน

public class AwesomeProfile implements Parcelable {
    @SerializedName("html_url")
    private String githubUrl;
    /* ... */
}

และเจ้าของบล็อกก็เจอบ่อยที่นักพัฒนาใช้ @SerializedName บ้างหรือไม่ใช้บ้างแบบนี้

public class AwesomeProfile implements Parcelable {
    private String login;
    private String id;
    @SerializedName("avatar_url")
    private String avatarUrl;
    @SerializedName("html_url")
    private String githubUrl;
    private String bio;
    private String email;
    private String blog;
    private String company;
    private String name;
    @SerializedName("public_repos")
    private int publicRepos;
    @SerializedName("public_gists")
    private int publicGists;
    private int followers;
    private int following;
    /* ... */
}

ถ้าดูเผินๆก็คงไม่คิดอะไรมาก เพราะว่าจะใช้ @SerializedName ไปทำไม ในเมื่อชื่อตัวแปรมันก็ตรงกับ Key ใน JSON อยู่แล้ว และมันก็สามารถทำงานได้ปกติสุข

จนกระทั่งใส่ ProGuard ตอน Release ขึ้น Production…

ปัญหาจะผุดขึ้นมาทันทีเมื่อต้องใส่ ProGuard ตอนจะเอาขึ้น Production เพราะว่าจะมีขั้นตอน Obfuscate ที่แปลงชื่อตัวแปรและชื่อคลาสให้อ่านยากขึ้น ดังนั้นเมื่อเอาไฟล์ Release APK มา Decompile ดู ก็จะเห็นโค้ดที่เปลี่ยนไปดังนี้

public class a implements Parcelable {
    private String a;
    private String b;
    @c(a = "avatar_url")
    private String c;
    @c(a = "html_url")
    private String d;
    private String e;
    private String f;
    private String g;
    private String h;
    private String i;
    @c(a = "public_repos")
    private int j;
    @c(a = "public_gists")
    private int k;
    private int l;
    private int m;
    /* ... */
}

จะเห็นว่าชื่อต่างๆถูกแปลงให้เป็นตัวอักษรสั้นๆทั้งหมดเลย ขนาด @SerializedName ยังถูกแปลงเป็น @c เลย

และนั่นหมายความว่า Gson จะแปลงข้อมูลผิดทันที เพราะว่าชื่อตัวแปรไม่ตรงกับ Key ใน JSON มีแค่อันที่ใส่ @SerializedName เท่านั้นที่ยังถูกต้อง เพราะชื่อ Key ที่กำหนดไว้ในนั้นเป็น String ธรรมดาๆ

เมื่อแปลงข้อมูลไม่ได้ ค่าที่ไม่ได้กำหนด @SerializedName ก็จะมีค่าเป็น Null ไปโดยปริยาย และทำให้แอปฯเกิด NullPointerException ได้ทันที

ใส่ @Keep เพื่อป้องกัน Obfuscate จาก ProGuard

โดยปกติแล้ว Model หรือ Data Class มักจะเป็นคลาสที่เก็บข้อมูลเพียงอย่างเดียว ไม่ได้มี Logic อะไรที่สำคัญอยู่ข้างใน ดังนั้นโดยปกติแล้วจึงไม่จำเป็นต้อง Obfuscate เลยซักนิดเดียว

ดังนั้นผู้ที่หลงเข้ามาอ่านจึงสามารถใช้ @Keep เพื่อเลี่ยงการ Obfuscate ได้เลย

@Keep
public class AwesomeProfile implements Parcelable {
    private String login;
    private String id;
    @SerializedName("avatar_url")
    private String avatarUrl;
    @SerializedName("html_url")
    /* ... */
}

หรือจะใส่ @SerializedName ให้ครบทุกตัวก็ได้ (แต่จะเหนื่อยกว่า)