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?
Gson gson = new GsonBuilder() . excludeFieldsWithoutExposeAnnotation() . create(); String jsonString = gson. toJson(source); assertEquals(expectedResult, jsonString);
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.
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 ...
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.
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
My first thought was to parse the JSON and hack it around but it looks like GSON JsonObject
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With