GSON: removing an unnecessary parent when deserializing

I am trying to deserialize a JSON array using GSON. All my nested objects are embedded in the inline object.

{ "Book": { "name": "Book 1", "published": 1999, "links": { "url": "www.book1.com" }, "embedded": { "Author": { "name": "John Doe", "links": { "url": "www.johndoe.com" } } } } } 

I may also have this situation:

 { "Book": { "name": "Book 1", "published": 1999, "links": { "url": "www.book1.com" }, "embedded": { "Publisher": { "name": "Publishing Company", "links": { "url": "www.publishingcompany.com" } } } } } 

This is a very simple example. Some of my objects can be nested in 2 or 3 levels, and all of them are in the "built-in" object. In addition, each object has a nested "url" inside the "links" object. I have about 20 different model objects, each with multiple fields, and each of them has a โ€œbuilt-inโ€ object. I started writing custom deserializers for each model, but that seems to have missed the whole point of using gson, and I don't always know what an inline object is.

I found this answer , but it was for serializing objects. I tried to figure this out for a while and did not find anything that worked.

My book model is as follows:

 public class Book { String name; int published; String url; Author author; Publisher publisher; } 

Author class:

 public class Author { String name; String url; } 

Publisher Class:

 public class Publisher { String name; String url; } 

And here is my book deserializer so far:

 public class BookDeserializer implements JsonDeserializer<Book> { @Override public Book deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { final JsonObject jsonObject = json.getAsJsonObject(); Book book = new Book(); book.setName(jsonObject.get("name").getAsString()); book.setPublished(jsonObject.get("published").getAsInt()); String url = jsonObject.getAsJsonObject("links").get("url").getAsString(); book.setUrl(url); // 1) How to get rid of this and skip to the "real" nested object? final JsonObject embeddedObject = jsonObject.getAsJsonObject("embedded"); // 2) See what the "embedded" object actually is. String embeddedModel; Set<Map.Entry<String, JsonElement>> entrySet = embeddedObject.entrySet(); for (Map.Entry<String, JsonElement> entry : entrySet) { // Author or Publisher embeddedModel = entry.getKey(); } // We have the model key, now add code here to deserialize whatever the object is return book; } } 

I still need to parse json and set each field for the book. Then I will need to add code to determine and use the correct deserializer for the nested object. It looks like I still need a special deserializer for each object to get the "url". I am new to gson, so maybe there is something that I am missing, but it seems that I could just manually parse all json and not even use gson. Maybe there is a way to smooth json?

Any ideas on how to parse this and still use the gson convenience, or is it even possible? Maybe Jackson can do it better?

+6
source share
3 answers

Create a class called embedded and add it as a field in the book:

 public class Book { String name; int published; Embedded embedded; } 

Then create an inline class:

 public class Embedded { Author Author; Publisher Publisher; } 

Just model your classes after JSON

+1
source

My first thought was to parse JSON and hack it, but GSON JsonObject seems to JsonObject immutable.

Therefore, I would write a simple stream analyzer that looks for "embedded": { and "links": { and removes them. Run a simple parenthesis counter to remove the matching parenthesis. If time permits, I can throw it together.

BTW. There is no comma in your JSON example - paste here to check it out.

Added: - Parsing the thread got out of control - although this would be a more convenient option. If you can find a JSON stream parser, such as SAX for XML, you can do it better.

The second mechanism assumes that you can put all of your JSON into a string in memory. Not an ideal, but probably an acceptable solution for most installations. Then a simple regular expression plus a parenthesis counter is used to remove the necessary parts.

 /** * Finds the first matching close brace - assuming an open brace has just been removed from the `start` position. */ private int closeBrace(StringBuilder s, int start) { int count = 1; boolean inQuotes = false; for (int i = start; i < s.length(); i++) { char ch = s.charAt(i); // Special case escapes. if (ch != '\\') { switch (ch) { case '"': inQuotes = !inQuotes; break; case '{': if (!inQuotes) { count += 1; } break; case '}': if (!inQuotes) { count -= 1; if (count == 0) { return i; } } break; } } else { // Escape character - skip the next character. if (i < s.length()) { i += 1; } } } // Failed to find return s.length(); } /** * Removes the JSON specified. */ private String hack(String json, String remove) { // Transfer to an sb for slicing and dicing. StringBuilder s = new StringBuilder(json); // Build my pattern Pattern p = Pattern.compile("\"" + remove + "\"\\s*:\\s*\\{"); // Make my Matchjer. Matcher m = p.matcher(s); // Is it there? while (m.find()) { int start = m.start(); int end = m.end(); // Kill the match. s.delete(start, end); // Walk forward to find the close brace. end = closeBrace(s, start); // And remove it. if (end < s.length()) { s.delete(end, end + 1); } // Rebuild the matcher. m = p.matcher(s); } return s.toString(); } private void test(String json) { JsonParser parser = new JsonParser(); JsonElement e = parser.parse(json); System.out.println(e); } public void test() { String json = "{'Book': {'name': 'Book \\'1\\'','published': 1999,'links': {'url': 'www.book1.com'},'embedded': {'Publisher': {'name': 'Publishing Company','links': {'url': 'www.publishingcompany.com'}}}}}".replace("'", "\""); test(json); json = hack(json, "embedded"); test(json); json = hack(json, "links"); test(json); } 

prints:

 {"Book":{"name":"Book \"1\"","published":1999,"links":{"url":"www.book1.com"},"embedded":{"Publisher":{"name":"Publishing Company","links":{"url":"www.publishingcompany.com"}}}}} {"Book":{"name":"Book \"1\"","published":1999,"links":{"url":"www.book1.com"},"Publisher":{"name":"Publishing Company","links":{"url":"www.publishingcompany.com"}}}} {"Book":{"name":"Book \"1\"","published":1999,"url":"www.book1.com","Publisher":{"name":"Publishing Company","url":"www.publishingcompany.com"}}} 

which is a bit like what you are looking for.

+1
source

I think you are looking for something like this: flatten

This tool can help you omit some of the built-in classes. You will have fewer classes and cleaner code. In your Books class, use this:

 @Flatten("embedded::Author") private Author author; 

This way you can collapse one level. In the same way, you can move links to the author. In Your Class By:

 @Flatten("links::url") private String url; 

If you want to go deeper, you can advance 2 levels higher in the same way. For instance:

 @Flatten("embedded::Author::name") private String authorName; 

Here you will have the name of the author in the book class.

Hope it helps.

0
source

Source: https://habr.com/ru/post/981503/


All Articles