Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GSON: Remove unnecessary parent object while deserializing

I am trying to deserialize a JSON array using GSON. All of my nested objects are embedded inside an "embedded" object.

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

I could also have a situation like this:

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

This is an extremely simple example. Some of my objects may be nested 2 or 3 levels deep, and all are in an "embedded" object. Also, each object has a nested "url" inside a "links" object. I have around 20 different model objects, each with several fields, and everyone of them have the "embedded" object. I started to write custom deserializers for each model, but that seems to miss the whole point of using gson, and I may not always know what the embedded object is.

I found this answer, but it was for serializing objects. I have been trying to figure this out for a while now and have not found anything that works.

My Book model looks like this:

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's key, now add code here to deserialize whatever the object is

        return book;
    }
}

I still have to parse the json and set each field for Book. Then, I would have to add code to determine and use the correct deserializer for the nested object. Looks like I still would need a custom deserializer for each object to get the "url". I am fairly new to gson, so maybe there is just something that I am overlooking, but it seems that I might as well just manually parse all of the json and not even use gson. Maybe there is a way to flatten out json?

Any ideas on how to parse this and still use the convenience of gson, or is this even possible? Maybe Jackson could handle this better?

like image 848
Mark Avatar asked Jan 25 '15 05:01

Mark


People also ask

How to exclude field in Gson?

Gson gson = new GsonBuilder() . excludeFieldsWithoutExposeAnnotation() . create(); String jsonString = gson. toJson(source); assertEquals(expectedResult, jsonString);

Does Gson ignore extra fields?

3. Deserialize JSON With Extra Unknown Fields to Object. As you can see, Gson will ignore the unknown fields and simply match the fields that it's able to.

How do I deserialize JSON with Gson?

Deserialization – Read JSON using Gson. Deserialization in the context of Gson means converting a JSON string to an equivalent Java object. In order to do the deserialization, we need a Gson object and call the function fromJson() and pass two parameters i.e. JSON string and expected java type after parsing is finished ...

Does Gson require empty constructor?

Gson will not work on objects with recursive references. Gson requires the class to have a default no-args constructor. If the no-args constructor is not provided, we can register an InstanceCreator with Gson, allowing us to deserialize instances of classes.


Video Answer


2 Answers

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

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

Then create an embedded class:

public class Embedded {
    Author Author;
    Publisher Publisher;
}

Just model your classes after your JSON

like image 68
user489041 Avatar answered Oct 12 '22 21:10

user489041


My first thought was to parse the JSON and hack it around but it looks like GSON JsonObjects are immutable.

I would therefore write a simple stream parser that looks for "embedded": { and "links": { and remove them. Run a simple bracket counter too to remove the matching close bracket. If time permits I might throw one together.

BTW - Your sample JSON is missing a comma - paste it here to check it.

Added:- The stream parser got out of hand - although it would have been the tidier option. If you can find a JSON stream parser like SAX does for XML you may be able to do it better that way.

Second mechanism assumes you can fit the whole of your JSON in a String in memory. Not ideal but probably an acceptable solution for most setups. This then uses a simple regex plus a bracket counter to remove the required 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 looks a bit like what you are looking for.

like image 24
OldCurmudgeon Avatar answered Oct 12 '22 21:10

OldCurmudgeon