ג'קסון מול גסון

1. הקדמה

במאמר זה נשווה את ממשקי ה- API של Gson ו- Jackson לצורך סידור וניתוק עריכת נתונים JSON לאובייקטים של Java ולהיפך.

Gson וג'קסון הן ספריות שלמות המציעות תמיכה בכריכת נתונים JSON בג'אווה. כל אחד מהם פרויקטים של קוד פתוח שפותח באופן פעיל המציעים טיפול בסוגי נתונים מורכבים ותמיכה ב- Java Generics.

וברוב המקרים, שתי הספריות יכולות לנתק מחדש ליישות מבלי לשנות מחלקה של ישות, וזה חשוב במקרים בהם למפתח אין גישה לקוד המקור של הישות.

2. תלות בגסון מייבן

 com.google.code.gson gson $ {gson.version} 

תוכל לקבל את הגרסה האחרונה של Gson כאן.

3. סדרת Gson

סידור ממיר אובייקטים של Java לפלט JSON. שקול את הגופים הבאים:

מעמד ציבורי ActorGson {פרטי מחרוזת imdbId; תאריך תאריך פרטיOfBirth; פילמוגרפיה רשימה פרטית; // גטרים וקובעים, בונה ברירת מחדל ובנאי שדה הושמט} סרט בכיתה ציבורית {פרטי מחרוזת imdbId; במאי מחרוזת פרטי; שחקני רשימה פרטיים; // גטרים וקובעים, בונה ברירת מחדל ובנאי שדה הושמט}

3.1. סידור פשוט

נתחיל בדוגמה של סדרת Java ל- JSON:

SimpleDateFormat sdf = SimpleDateFormat חדש ("dd-MM-yyyy"); ActorGson rudyYoungblood = ActorGson חדש ("nm2199632", sdf.parse ("21-09-1982"), Arrays.asList ("אפוקליפטו", "Beatdown", "הולכי הרוח")); סרט סרט = סרט חדש ("tt0472043", "מל גיבסון", Arrays.asList (rudyYoungblood)); מחרוזת serializedMovie = חדש Gson (). ToJson (סרט);

זה יביא ל:

{"imdbId": "tt0472043", "במאי": "מל גיבסון", "שחקנים": [{"imdbId": "nm2199632", "dateOfBirth": "21 בספטמבר 1982 12:00:00 בבוקר", " פילמוגרפיה ": [" Apocalypto "," Beatdown "," Wind Winders "]}}}

כברירת מחדל:

  • כל המאפיינים מסודרים בסידור מכיוון שאין להם ריק ערכים
  • תאריך לידה שדה תורגם בתבנית ברירת המחדל של תאריך Gson
  • הפלט אינו מעוצב ושמות מאפייני JSON תואמים לישויות Java

3.2. סידור מותאם אישית

השימוש בסידור מותאם אישית מאפשר לנו לשנות את ההתנהגות הסטנדרטית. אנו יכולים להציג מעצב פלט עם HTML, ידית ריק ערכים, אל תכלול מאפיינים מהפלט, או הוסף פלט חדש.

ActorGsonSerializer משנה את יצירת קוד JSON עבור ActorGson אֵלֵמֶנט:

מחלקה ציבורית ActorGsonSerializer מיישם את JsonSerializer {פרטי SimpleDateFormat sdf = חדש SimpleDateFormat ("dd-MM-yyyy"); @Override JsonElement ציבורי סדרתי (ActorGson actor, סוג סוג, JsonSerializationContext jsonSerializationContext) {JsonObject actorJsonObj = new JsonObject (); שחקן JsonObj.addProperty ("קוד IMDB", actor.getImdbId ()); actorJsonObj.addProperty ("תאריך לידה", actor.getDateOfBirth ()! = null? sdf.format (actor.getDateOfBirth ()): null); actorJsonObj.addProperty ("סרט °: ", actor.getFilmography ()! = null? actor.getFilmography (). size (): null); actorJsonObj.addProperty (" פילמוגרפיה ", actor.getFilmography ()! = null? convertFilmography (actor.getFilmography ()): null); return actorJsonObj;} private String convertFilmography (List filmography) {return filmography.stream () .collect (Collectors.joining ("-"));}}

על מנת לא לכלול את מְנַהֵל רכוש, ה @לַחשׂוֹף הערה משמשת למאפיינים שאנחנו רוצים לשקול:

בכיתה ציבורית MovieWithNullValue {@Expose מחרוזת פרטית imdbId; במאי מחרוזת פרטי; @ חשוף שחקנים פרטיים ברשימה; }

כעת נוכל להמשיך ביצירת אובייקטים של Gson באמצעות ה- GsonBuilder מעמד:

Gson gson = GsonBuilder חדש () .setPrettyPrinting () .excludeFieldsWithoutExposeAnnotation () .serializeNulls () .disableHtmlEscaping () .registerTypeAdapter (ActorGson.class, ActorGsonSerializer חדש ()) .create () SimpleDateFormat sdf = SimpleDateFormat חדש ("dd-MM-yyyy"); ActorGson rudyYoungblood = ActorGson חדש ("nm2199632", sdf.parse ("21-09-1982"), Arrays.asList ("אפוקליפטו", "Beatdown", "הולכי הרוח")); MovieWithNullValue movieWithNullValue = MovieWithNullValue חדש (null, "מל גיבסון", Arrays.asList (rudyYoungblood)); מחרוזת serializedMovie = gson.toJson (movieWithNullValue);

התוצאה היא הבאה:

{"imdbId": null, "שחקנים": [{"קוד IMDB":" nm2199632 ","תאריך לידה": "21-09-1982", "סרט °: ": 3," filmography ":" Apocalypto-Beatdown-Wind Walkers "}]}

שים לב ש:

  • הפלט מעוצב
  • חלק משמות המאפיינים משתנים ומכילים HTML
  • ריק הערכים כלולים, וה- מְנַהֵל שדה מושמט
  • תַאֲרִיך נמצא כעת ב dd-MM-yyyy פוּרמָט
  • קיים נכס חדש - סרט לא
  • פילמוגרפיה היא מאפיין מעוצב, לא ברירת המחדל של JSON

4. התפטרות Gson

4.1. התנערות פשוטה

התנערות ממיר קלט JSON לאובייקטים של Java. כדי להמחיש את התפוקה, אנו מיישמים את ה- toString () שיטה בשני שיעורי הישות:

סרט בכיתה ציבורית {@Override public String toString () {return "Movie [imdbId =" + imdbId + ", במאי =" + במאי + ​​", שחקנים =" + שחקנים + "]"; } ...} מעמד ציבורי ActorGson {@Override public String toString () {return "ActorGson [imdbId =" + imdbId + ", dateOfBirth =" + dateOfBirth + ", פילמוגרפיה =" + פילמוגרפיה + "]"; } ...}

לאחר מכן אנו משתמשים ב- JSON הסידורי ומריצים אותו באמצעות עריקת ייעול רגילה של Gson:

מחרוזת jsonInput = "{\" imdbId \ ": \" tt0472043 \ ", \" שחקנים \ ":" + "[{\" imdbId \ ": \" nm2199632 \ ", \" dateOfBirth \ ": \" 1982- 09-21T12: 00: 00 + 01: 00 \ "," + "\" פילמוגרפיה \ ": [\" אפוקליפטו \ ", \" Beatdown \ ", \" הליכי רוח \ "]}]}"; סרט outputMovie = Gson חדש (). מאת Json (jsonInput, Movie.class); outputMovie.toString ();

הפלט הוא אנו היישויות שלנו, המאוכלסות בנתוני קלט JSON שלנו:

סרט [imdbId = tt0472043, במאי = null, שחקנים = [ActorGson [imdbId = nm2199632, dateOfBirth = יום שלישי 21 בספטמבר 04:00:00 PDT 1982, פילמוגרפיה = [אפוקליפטו, ביטדאון, הולכי רוח]]]]

כפי שהיה המקרה עם הסידור הפשוט:

  • שמות קלט JSON חייבים להתאים עם שמות ישויות Java, או שהם מוגדרים לאפס.
  • תאריך לידה שדה תורגם בתבנית ברירת המחדל של תאריך Gson, תוך התעלמות מאזור הזמן.

4.2. התפטרות בהתאמה אישית

שימוש ב deserializer מותאם אישית מאפשר לנו לשנות את התנהגות ה- deserializer הרגילה. במקרה זה, אנו רוצים שהתאריך ישקף את אזור הזמן הנכון עבור תאריך לידה. אנו משתמשים בהתאמה אישית ActorGsonDeserializer על ActorGson ישות להשיג זאת:

מחלקה ציבורית ActorGsonDeserializer מיישם את JsonDeserializer {פרטי SimpleDateFormat sdf = SimpleDateFormat חדש ("yyyy-MM-dd'T'HH: mm: ss"); @Override Public ActorGson deserialize (JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) זורק את JsonParseException {JsonObject jsonObject = json.getAsJsonObject (); JsonElement jsonImdbId = jsonObject.get ("imdbId"); JsonElement jsonDateOfBirth = jsonObject.get ("dateOfBirth"); JsonArray jsonFilmography = jsonObject.getAsJsonArray ("פילמוגרפיה"); ArrayList filmList = ArrayList חדש (); אם (jsonFilmography! = null) {עבור (int i = 0; i <jsonFilmography.size (); i ++) {filmList.add (jsonFilmography.get (i) .getAsString ()); }} ActorGson actorGson = ActorGson חדש (jsonImdbId.getAsString (), sdf.parse (jsonDateOfBirth.getAsString ()), filmList); שחקן חזרה Gson; }}

העסקנו א SimpleDateFormat מנתח לניתוח תאריך הקלט, תוך התחשבות באזור הזמן.

שים לב שיכולנו להחליט פשוט לכתוב deserializer מותאם אישית רק לתאריך, אלא ל ActorGsonDeserializer מציע תצוגה מפורטת יותר של תהליך ההתנקשות.

שים לב גם כי גישת Gson אינה מחייבת שינוי ה- ActorGson ישות, שהיא אידיאלית מכיוון שלא תמיד תהיה לנו גישה ליישות הקלט. אנו משתמשים ב deserializer המותאם אישית כאן:

מחרוזת jsonInput = "{\" imdbId \ ": \" tt0472043 \ ", \" שחקנים \ ":" + "[{\" imdbId \ ": \" nm2199632 \ ", \" dateOfBirth \ ": \" 1982- 09-21T12: 00: 00 + 01: 00 \ ", + \" פילמוגרפיה \ ": [\" אפוקליפטו \ ", \" ביטדאון \ ", \" הולכי רוח \ "]}]}"; Gson gson = GsonBuilder חדש () .registerTypeAdapter (ActorGson.class, ActorGsonDeserializer חדש ()) .create (); סרט outputMovie = gson.fromJson (jsonInput, Movie.class); outputMovie.toString ();

הפלט דומה לתוצאת ה- deserializer הפשוטה, למעט התאריך שמשתמש באזור הזמן הנכון:

סרט [imdbId = tt0472043, במאי = null, שחקנים = [ActorGson [imdbId = nm2199632, dateOfBirth = יום שלישי 21 בספטמבר 12:00:00 PDT 1982, פילמוגרפיה = [אפוקליפטו, ביטדאון, הולכי רוח]]]]

5. תלות בג'קסון מייבן

 com.fasterxml.jackson.core jackson-databind $ {jackson.version} 

תוכל לקבל את הגרסה האחרונה של ג'קסון כאן.

6. סדרת ג'קסון

6.1. סידור פשוט

כאן נשתמש בג'קסון כדי להשיג את אותו תוכן סדרתי שהיה לנו עם Gson באמצעות הגופים הבאים. שים לב שקבוצות / קביעות הישות חייבות להיות ציבוריות:

מעמד ציבורי ActorJackson {private String imdbId; תאריך תאריך פרטיOfBirth; פילמוגרפיה רשימה פרטית; // נדרשים וקובעים, בנאי ברירת מחדל // ופרטי בנאי שדה הושמטו} סרט בכיתה ציבורית {פרטי מחרוזת imdbId; במאי מחרוזת פרטי; שחקני רשימה פרטיים; // הגטרים והגדרים הנדרשים, בנאי ברירת המחדל // ופרטי בנאי השדה הושמטו} SimpleDateFormat sdf = SimpleDateFormat חדש ("dd-MM-yyyy"); שחקן ג'קסון rudyYoungblood = שחקן ג'קסון חדש ("nm2199632", sdf.parse ("21-09-1982"), Arrays.asList ("אפוקליפטו", "ביטדאון", "הולכי רוח")); סרט סרט = סרט חדש ("tt0472043", "מל גיבסון", Arrays.asList (rudyYoungblood)); ממפה ObjectMapper = ObjectMapper חדש (); מחרוזת jsonResult = mapper.writeValueAsString (סרט);

התפוקה היא כדלקמן:

{"imdbId": "tt0472043", "במאי": "מל גיבסון", "שחקנים": [{"imdbId": "nm2199632", "dateOfBirth": 401439600000, "פילמוגרפיה": ["אפוקליפטו", "Beatdown" , "הליכי רוח"]}]}

כמה הערות מעניינות:

  • ObjectMapper הוא הסידור / deserializer של ג'קסון שלנו
  • הפלט JSON אינו מעוצב
  • כברירת מחדל, Java Date מתורגם ל- ארוך ערך

6.2. סידור מותאם אישית

אנחנו יכולים ליצור סדרת ג'קסון עבור שחקן ג'קסון יצירת אלמנטים על ידי הרחבת StdSerializer עבור היישות שלנו. שים לב שוב כי הקבוצות / קובעי הישות חייבים להיות ציבוריים:

מחלקה ציבורית ActorJacksonSerializer מרחיב את StdSerializer {פרטי SimpleDateFormat sdf = חדש SimpleDateFormat ("dd-MM-yyyy"); ActorJacksonSerializer ציבורי (Class t) {super (t); } @Override בטל פומבי בסידור (שחקן ג'קסון, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) זורק IOException {jsonGenerator.writeStartObject (); jsonGenerator.writeStringField ("imdbId", actor.getImdbId ()); jsonGenerator.writeObjectField ("dateOfBirth", actor.getDateOfBirth ()! = null? sdf.format (actor.getDateOfBirth ()): null); jsonGenerator.writeNumberField ("N ° Film:", actor.getFilmography ()! = null? actor.getFilmography (). size (): null); jsonGenerator.writeStringField ("פילמוגרפיה", שחקן. getFilmography () .stream (). collect (Collectors.joining ("-"))); jsonGenerator.writeEndObject (); }}

אנו יוצרים ישות סרט כדי לאפשר התעלמות מה- מְנַהֵל שדה:

בכיתה ציבורית MovieWithNullValue {פרטי מחרוזת imdbId; @JsonIgnore מנהל מחרוזת פרטי; שחקני רשימה פרטיים; // נדרשים מגדרים וקובעים, בנאי ברירת מחדל // ופרטי בנאי שדה הושמטו}

עכשיו נוכל להמשיך בהתאמה אישית ObjectMapper יצירה והגדרה:

SimpleDateFormat sdf = SimpleDateFormat חדש ("dd-MM-yyyy"); שחקן ג'קסון rudyYoungblood = שחקן ג'קסון חדש ("nm2199632", sdf.parse ("21-09-1982"), Arrays.asList ("אפוקליפטו", "ביטדאון", "הולכי רוח")); MovieWithNullValue movieWithNullValue = MovieWithNullValue חדש (null, "מל גיבסון", Arrays.asList (rudyYoungblood)); מודול SimpleModule = SimpleModule חדש (); module.addSerializer (ActorJacksonSerializer חדש (ActorJackson.class)); ממפה ObjectMapper = ObjectMapper חדש (); מחרוזת jsonResult = mapper.registerModule (מודול) .writer (DefaultPrettyPrinter חדש ()) .writeValueAsString (movieWithNullValue);

הפלט מעוצב JSON המטפל ריק ערכים, מעצב את התאריך, לא כולל את מְנַהֵל שדה ומציג פלט חדש של לא:

{"שחקנים": [{"imdbId": "nm2199632", "dateOfBirth": "21-09-1982", "N ° Film:": 3, "filmography": "Apocalypto-Beatdown-Wind Walkers"}] , "imdbID": null}

7. התפטרות ג'קסון

7.1. התנערות פשוטה

כדי להמחיש את התפוקה, אנו מיישמים את ה- toString () בשתי שיעורי הישות של ג'קסון:

סרט בכיתה ציבורית {@Override public String toString () {return "Movie [imdbId =" + imdbId + ", במאי =" + במאי + ​​", שחקנים =" + שחקנים + "]"; } ...} מעמד ציבורי ActorJackson {@Override public String toString () {return "ActorJackson [imdbId =" + imdbId + ", dateOfBirth =" + dateOfBirth + ", פילמוגרפיה =" + פילמוגרפיה + "]"; } ...}

לאחר מכן אנו משתמשים ב- JSON בסידרה ומפעילים אותו באמצעות התנערות מג'קסון:

מחרוזת jsonInput = "{\" imdbId \ ": \" tt0472043 \ ", \" שחקנים \ ": [{\" imdbId \ ": \" nm2199632 \ ", \" dateOfBirth \ ": \" 1982-09-21T12 : 00: 00 + 01: 00 \ ", \" פילמוגרפיה \ ": [\" אפוקליפטו \ ", \" Beatdown \ ", \" הליכי רוח \ "]}]}"; ממפה ObjectMapper = ObjectMapper חדש (); סרט סרט = mapper.readValue (jsonInput, Movie.class);

הפלט הוא אנו היישויות שלנו, המאוכלסות בנתוני קלט JSON שלנו:

סרט [imdbId = tt0472043, במאי = null, שחקנים = [שחקן ג'קסון [imdbId = nm2199632, dateOfBirth = יום שלישי 21 בספטמבר 04:00:00 PDT 1982, פילמוגרפיה = [אפוקליפטו, ביטדאון, הולכי רוח]]]]

כמו שהיה המקרה עם הסידורי פשוט:

  • שמות קלט JSON חייבים להתאים עם שמות ישויות Java, או שהם מוגדרים ל- ריק,
  • תאריך לידה שדה תורגם בתבנית ברירת המחדל של תאריך ג'קסון, תוך התעלמות מאזור הזמן.

7.2. התפטרות בהתאמה אישית

השימוש בהתאמה אישית של deserializer מאפשר לנו לשנות את התנהגות ה- deserializer הרגילה.

במקרה זה, אנו רוצים שהתאריך ישקף את אזור הזמן הנכון עבור תאריך לידה, אז אנו מוסיפים DateFormatter לג'קסון שלנו ObjectMapper:

מחרוזת jsonInput = "{\" imdbId \ ": \" tt0472043 \ ", \" במאי \ ": \" מל גיבסון \ ", \" שחקנים \ ": [{\" imdbId \ ": \" nm2199632 \ ", \ "dateOfBirth \": \ "1982-09-21T12: 00: 00 + 01: 00 \", \ "פילמוגרפיה \": [\ "אפוקליפטו \", \ "ביטדאון \", \ "הולכי רוח \"] }]} "; ממפה ObjectMapper = ObjectMapper חדש (); DateFormat df = SimpleDateFormat חדש ("yyyy-MM-dd'T'HH: mm: ss"); mapper.setDateFormat (df); סרט סרט = mapper.readValue (jsonInput, Movie.class); movie.toString ();

הפלט משקף את אזור הזמן הנכון עם התאריך:

סרט [imdbId = tt0472043, במאי = מל גיבסון, שחקנים = [השחקן ג'קסון [imdbId = nm2199632, dateOfBirth = יום שלישי 21 בספטמבר 12:00:00 PDT 1982, פילמוגרפיה = [אפוקליפטו, ביטדאון, הולכי רוח]]]]

פיתרון זה נקי ופשוט.

לחלופין, יכולנו ליצור deserializer מותאם אישית עבור שחקן ג'קסון בכיתה, רשם מודול זה אצלנו ObjectMapper, וערך מחדש את התאריך באמצעות @JsonDeserialize ביאור על שחקן ג'קסון יֵשׁוּת.

החיסרון בגישה זו הוא הצורך בשינוי הישות, מה שאולי לא יהיה אידיאלי למקרים בהם אין לנו גישה למחלקות ישויות הקלט.

8. מסקנה

גם Gson וגם ג'קסון הן אפשרויות טובות לסידור / עריקת נתוני JSON, פשוטות לשימוש ומתועדות היטב.

היתרונות של Gson:

  • הפשטות של toJson/מג'ייסון במקרים הפשוטים
  • לצורך עריקת ייעול, אין צורך בגישה לגופי Java

יתרונותיו של ג'קסון:

  • מובנה בכל JAX-RS (ג'רזי, אפאצ'י CXF, RESTEasy, Restlet) ומסגרת האביב
  • תמיכה בהערות נרחבת

אתה יכול למצוא את הקוד של Gson ו- Jackson ב- GitHub.