JAX-RS/Jackson -- Deserialize JSON with Unknown Root Element Name? - json

I am writing a RESTeasy Proxy Client to consume Apple's API for retrieving their iTunes category list. When you query for information about a given category , for example with this URL:
https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/genres?id=1420
...you get a JSON response that looks like this:
{
"1420":{
"name":"Self-Help",
"id":"1420",
"url":"https://itunes.apple.com/us/genre/podcasts-health-self-help/id1420?mt=2",
"rssUrls":{
"topVideoPodcastEpisodes":"https://itunes.apple.com/us/rss/topvideopodcastepisodes/genre=1420/json",
"topAudioPodcasts":"https://itunes.apple.com/us/rss/topaudiopodcasts/genre=1420/json",
"topVideoPodcasts":"https://itunes.apple.com/us/rss/topvideopodcasts/genre=1420/json",
"topPodcasts":"https://itunes.apple.com/us/rss/toppodcasts/genre=1420/json",
"topAudioPodcastEpisodes":"https://itunes.apple.com/us/rss/topaudiopodcastepisodes/genre=1420/json",
"topPodcastEpisodes":"https://itunes.apple.com/us/rss/toppodcastepisodes/genre=1420/json"
},
"chartUrls":{
"videoPodcastEpisodes":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=VideoPodcastEpisodes",
"podcasts":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=Podcasts",
"audioPodcastEpisodes":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=AudioPodcastEpisodes",
"audioPodcasts":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=AudioPodcasts",
"podcastEpisodes":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=PodcastEpisodes",
"videoPodcasts":"https://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/charts?cc=us&g=1420&name=VideoPodcasts"
}
}
}
I am trying to map this JSON response to a Java object using JAXB and Jackson. However, the "1420" root element name seems to be causing a problem, as I get the following exception when calling my client:
Unrecognized field "1420" (class foo.bar.ITunesCategoryList), not marked as ignorable
My JAXB class looks like this:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ITunesCategory implements TransferObject {
private static final long serialVersionUID = 3443545925023804457L;
#XmlElement(name = "id")
#JsonProperty("id")
private String identifier = null;
#XmlElement
private String name = null;
#XmlElementWrapper(name = "subgenres")
private List<ITunesCategory> subcategories = new ArrayList<ITunesCategory>();
...
}
I've even tried creating a wrapper class since the search could result in more than one category being returned. It looks like this:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ITunesCategoryList implements TransferObject {
private static final long serialVersionUID = 3303125979016445238L;
#XmlElement
private List<ITunesCategory> categories = new ArrayList<ITunesCategory>();
...
}
However, regardless of which class I specify as my return type, I get the same error because the category identifier is the root element name of the JSON object.
Is there any way to tell JAXB/Jackson/JAX-RS/RESTeasy to ignore the root element name and just map the underlying object to Java? There is no way for me to know the root element name at develop/compile time, since it corresponds directly to the results returned by the search. Is there anything that can be done to get around this exception? Thanks for any help you can give!

I couldn't find much on dynamically ignoring the root, at least not anything that would be suitable in a JAX-RS environment. The only thing I could think is to write a custom deserializer, and just skip the root node. Something like
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.Map;
public abstract class IHateRootElemsJsonDeserializer<T> extends JsonDeserializer<T> {
private final ObjectMapper mapper = new ObjectMapper();
private final Class<T> cls;
public IHateRootElemsJsonDeserializer(Class<T> cls) {
this.cls = cls;
}
#Override
public T deserialize(JsonParser jp, DeserializationContext dc)
throws IOException, JsonProcessingException {
JsonNode rootNode = jp.getCodec().readTree(jp);
Map.Entry<String,JsonNode> field = rootNode.fields().next();
JsonNode node = field.getValue();
T result = mapper.convertValue(node, cls);
return result;
}
}
Then just extend it with a concrete type.
public class GenreDeserializer extends IHateRootElemsJsonDeserializer<Genre>{
public GenreDeserializer() {
super(Genre.class);
}
}
Here's a test using the exact JSON you provided above
public class Test {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
GenreDeserializer deserializer = new GenreDeserializer();
SimpleModule module = new SimpleModule();
module.addDeserializer(Genre.class, deserializer);
mapper.registerModule(module);
Genre genre = mapper.readValue(JSON_FILE, Genre.class);
System.out.println(genre);
genre = mapper.readValue(JSON_FILE, Genre.class);
System.out.println(genre);
}
static final File JSON_FILE = new File("json.json");
}
The model
public class Genre {
public String id;
public String name;
public String url;
public RssUrls rssUrls;
public ChartUrls chartUrls;
#Override
public String toString() {
return "Category{" + "id=" + id + ", name="
+ name + ", url=" + url + ", rssUrls=" + rssUrls + '}';
}
public static class RssUrls {
public String topVideoPodcastEpisodes;
public String topAudioPodcasts;
public String topVideoPodcasts;
public String topPodcasts;
public String topAudioPodcastEpisodes;
public String topPodcastEpisodes;
#Override
public String toString() {
return "RssUrls{" + "topVideoPodcastEpisodes=" + topVideoPodcastEpisodes
+ ", topAudioPodcasts=" + topAudioPodcasts
+ ", topVideoPodcasts=" + topVideoPodcasts
+ ", topPodcasts=" + topPodcasts
+ ", topAudioPodcastEpisodes=" + topAudioPodcastEpisodes
+ ", topPodcastEpisodes=" + topPodcastEpisodes + '}';
}
}
public static class ChartUrls {
public String videoPodcastEpisodes;
public String podcasts;
public String audioPodcastEpisodes;
public String audioPodcasts;
public String podcastEpisodes;
public String videoPodcasts;
#Override
public String toString() {
return "ChatUrls{" + "videoPodcastEpisodes=" + videoPodcastEpisodes
+ ", podcasts=" + podcasts
+ ", audioPodcastEpisodes=" + audioPodcastEpisodes
+ ", audioPodcasts=" + audioPodcasts
+ ", podcastEpisodes=" + podcastEpisodes
+ ", videoPodcasts=" + videoPodcasts + '}';
}
}
}
To configure the ObjectMapper in JAX-RS, you can have a look at this post

Related

Spring sends empty JSON despite of object being not null

In my controller I have the following method:
#RequestMapping(value = "/getAll", method = RequestMethod.GET)
public List<Topic> getAllTopics() {
List<Topic> allTopics = service.getAllTopics();
assert allTopics.size() > 0; // is not empty
System.out.println(allTopics.get(0)); // Topic{id=1, name='bla', description='blahhh'}
return allTopics;
}
When I go to http://localhost:8080/getAll I get [{},{},{},{}] as a result but service.getAllTopics() returns non empty List So the list to be send is not empty but the browser receives invalid JSON. However, there is no problem in serializing objects since the following method return valid JSON. What's the problem?
#GetMapping("/json")
public List<Locale> getLocales() {
return Arrays.asList(DateFormat.getAvailableLocales());
}
I'm running latest Spring Boot, i.e. 2.1.3.RELEASE.
Update
Here's my entity class - Topic
#Entity
#Table(name = "topic", schema="tetra")
public class Topic {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String description;
public Topic() {
}
public Topic(String name, String description) {
this.name = name;
this.description = description;
}
#Override
public String toString() {
return "Topic{" +
"id=" + id +
", name='" + name + '\'' +
", description='" + description + '\'' +
'}';
}
}
By default , Jackson will only serialise the public fields and public getters into JSON. As the Topic neither have public fields nor the public getter , nothing will be serialised and you get an empty JSON object.
There are plenty of ways to configure it such as:
(1) Simply add public getter for all fields
(2) Use #JsonAutoDetect(fieldVisibility = Visibility.ANY) such that private fields can also be auto detected :
#Entity
#Table(name = "topic", schema="tetra")
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class Topic {
}
(3) Use #JsonProperty to explicitly pick what fields/getter to be serialised .The nice things of this approach is that the field name in JSON can be different from the POJO :
#Entity
#Table(name = "topic", schema="tetra")
public class Topic {
#JsonProperty("id")
private Integer id;
#JsonProperty("name")
private String name;
#JsonProperty("description")
private String description;
}

Convert JSON String to JSON

I am querying Postgres DB for retrieving JSONB format data using spring data JPA, which is stored like this in DB :
"{
"name":"abc",
"place"="xyz"
}"
but I am getting the response back with out double quotes, is there a way that I get the double quotes using spring data JPA or convert back to JSON with double quotes?
I tried new GSON().tJson() and ObjectMapper but no luck any help is appreciated.
Thanks
Your JSON object format is quite good and you should have no issue deserializing it back to a POJO.
Assuming your POJO class is named Template, here down a sample de-serialization implementation:
public class SerializationTest {
private static final String OBJECT = "{\n"
+ "\"name\":\"abc\",\n"+
"\"place\"=\"xyz\"\n"+
"}";
public static void main(String[] args) {
System.out.println("Serialized object:\n" + OBJECT);
Gson gson = new Gson();
Template template = gson.fromJson(OBJECT, Template.class);
System.out.println(template);
}
private static class Template {
private String name;
private String place;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPlace() {
return place;
}
public void setPlace(String place) {
this.place = place;
}
#Override
public String toString() {
return "Template{" +
"name='" + name + '\'' +
", place='" + place + '\'' +
'}';
}
}
}

Using Custom JSON Deserialization in Jackson

I am trying to create objects of a complex class with the Jackson library. Each object has a schema that the deserializer needs to use to interpret the JSON. My question is how to supply the schema to the deserializer?
The deserializer extends the class JSONDeserializer, which has a no-argument constructor and an abstract method deserialize(parser,context) that must be overridden. I would like to use instead the alternate method deserialize(parser,context, value) where value is the partially constructed object, which includes the schema. That is, the deserialize method could call value.schema() to access the schema. The object itself is constructed piecewise with a builder, which the alternate method uses.
I have found no documentation on how to register the alternate deserialize method with the object mapper to insure that it, rather than the overridden abstract method, is called.
Any advice would be appreciated.
So Lets say you have a class called User which has a which has a property called Data which has date field birthdate, and you don't want to use the standard Date deserializer and wants to use your custom one. below is how it can be achieved.
User.java
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class User {
#JsonProperty("code")
public Integer code;
#JsonProperty("status")
public String status;
#JsonProperty("message")
public String message;
#JsonProperty("time")
public String time;
#JsonProperty("data")
public Data data;
}
Data.java
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Data {
#JsonProperty("id_hash")
public Integer idHash;
#JsonProperty("user_name")
public String userName;
#JsonProperty("user_surname")
public String userSurname;
#JsonProperty("birthdate")
#JsonDeserialize(using = BirthdayDeserializer.class)
public Date birthdate;
#JsonProperty("height")
public Integer height;
#JsonProperty("weight")
public Integer weight;
#JsonProperty("sex")
public Integer sex;
#JsonProperty("photo_path")
public String photoPath;
}
BirthdayDeserializer.java
public class BirthdayDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jsonparser, DeserializationContext deserializationcontext) throws IOException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = jsonparser.getText();
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
Main.java to test it.
public class Main {
public static void main(String[] args) throws IOException {
String json = "{\n" +
" \"code\": 1012,\n" +
" \"status\": \"sucess\",\n" +
" \"message\": \"Datos Del Usuario\",\n" +
" \"time\": \"28-10-2015 10:42:04\",\n" +
" \"data\": {\n" +
" \"id_hash\": 977417640,\n" +
" \"user_name\": \"Daniel\",\n" +
" \"user_surname\": \"Hdz Iglesias\",\n" +
" \"birthdate\": \"1990-02-07\",\n" +
" \"height\": 190,\n" +
" \"weight\": 80,\n" +
" \"sex\": 2\n" +
" }\n" +
"}";
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new BirthdayDeserializer());
mapper.registerModule(module);
User readValue = mapper.readValue(json, User.class);
System.out.println(readValue);
}
}
Check the main method as how I have registered the custom Deserializer.

Deserializing JSON into typed class

I have a base class and two sublcasses:
public /*abstract*/ class TargetPoi {
private String poiId;
...
}
public class TargetSub1Poi extends TargetPoi {
private String oneMoreId;
...
}
public class TargetSub2Poi extends TargetPoi {
...
}
Is it possible to declare the base class abstract? ...I always get an exception when a JSON is send with the request if I use the abstract keyword...
Exception Description: This class does not define a public default constructor, or the constructor raised an exception.
Internal Exception: java.lang.InstantiationException
Descriptor: XMLDescriptor(com.foo.bar.TargetPoi --> [])
When the POST request with its JSON in the request body is coming into the Jersy Resource I want to deserialize the JSON into the proper TargetPoi subclasses.
The JSON:
{
"requestId": "84137f1ab38f4bf585d13984fc07c621",
"startTime": "2013-10-30T18:30:00+02:00",
"endTime": "2013-10-30T18:45:00+02:00",
"targetPoi":
{
"poiId": "0000000602",
"oneMoreId": "1"
},
"type": "Block",
"notification": true
}
My Resource hast a method defined this way...
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response doReservation(final ReservationDO reservationDO
, #QueryParam("serviceType") final String serviceType) {
...
}
The JSON shall be deserialized in this class:
#XmlRootElement
public class ReservationDO<T extends TargetPoi>
{
public String requestId;
public String startTime;
public String endTime;
public String serviceType;
public T targetPoi;
...
}
How can I tell Jackson to bind the JSON for the targetPoi properly to the correct subtype (TargetSub1Poi)? The serviceType could tell me to which subtype the targetPoi is to bind to...but I think this information can't be used from Jackson, does it? When I print out the deserialized JSON in th edoreservation method the oneMoreId part coming with the original JSON is lost.
Do I have to provide any TypeInfo or can I achieve it without?
It is possible to declare your parent class abstract and have a generic field. You just need to tell Jackson which sub-type to use to create an instance of you object. It can be done by using #JsonTypeInfo annotation as described at the Jackson polymorphic deserialization wiki page.
Here is an example:
public class JacksonPoi {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY, property = "type")
public static abstract class TargetPoi {
public String poiId;
}
#JsonTypeName("sub1")
public static class TargetSub1Poi extends TargetPoi {
public String oneMoreId;
#Override
public String toString() {
return "TargetSub1Poi{" +
"poiId='" + poiId + '\'' +
"oneMoreId='" + oneMoreId + '\'' +
'}';
}
}
#JsonTypeName("sub2")
public static class TargetSub2Poi extends TargetPoi {
public String twoMoreId;
#Override
public String toString() {
return "TargetSub2Poi{" +
"poiId='" + poiId + '\'' +
"twoMoreId='" + twoMoreId + '\'' +
'}';
}
}
public static class Bean<T extends TargetPoi> {
public String field;
public T poi;
#Override
public String toString() {
return "Bean{" +
"field='" + field + '\'' +
", poi=" + poi +
'}';
}
}
public static final String JSON = "{\n" +
"\"poi\": \n" +
"{\n" +
" \"type\": \"sub1\",\n" +
" \"poiId\": \"0000000602\",\n" +
" \"oneMoreId\": \"1\"\n" +
"},\n" +
"\"field\": \"value1\"\n" +
"}";
public static final String JSON2 = "{\n" +
"\"poi\": \n" +
"{\n" +
" \"type\": \"sub2\",\n" +
" \"poiId\": \"0000000602\",\n" +
" \"twoMoreId\": \"13\"\n" +
"},\n" +
"\"field\": \"value2\"\n" +
"}";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.registerSubtypes(TargetSub1Poi.class, TargetSub2Poi.class);
Bean<TargetSub1Poi> poi1 = mapper.readValue(JSON, new TypeReference<Bean<TargetSub1Poi>>() {});
System.out.println(poi1);
Bean<TargetSub2Poi> poi2 = mapper.readValue(JSON2, new TypeReference<Bean<TargetSub2Poi>>() {});
System.out.println(poi2);
}
}
Output:
Bean{field='value1', poi=TargetSub1Poi{poiId='0000000602'oneMoreId='1'}}
Bean{field='value2', poi=TargetSub2Poi{poiId='0000000602'twoMoreId='13'}}

Java unmarshilling JSON data containg abstract type

We are using Jersey/Jackson to unmarshall JSON data to java DTOs. One of my DTO is an abstract class, and i would like to unmarshall the JSON data to one of his extended DTO. For example, assuming i have these DTOs :
public abstract class AnimalDTO{}
public class DogDTO extends AnimalDTO{}
public class CatDTO extends AnimalDTO{}
I would like to unmarshall this JSON data:
{Zoo: {Animals:[{"type"="DogDTO", "code"="001", "name"="chihuahua"}, {"type"="CatDTO", "code"="002", "name"="felix"}]}}
As "type" would give the type of DTO i would like to unmarshall to. But it seems that this property isn't considered. Is there something I missed, or mistook in the JSON syntax?
Thanks.
In your case you should use #JsonTypeInfo annotation.
For more information, please see below links:
JacksonFAQ.
Jackson 1.5: Polymorphic Type Handling, first steps.
Using above links I have created a simple example which serialize POJO objects with class names:
import java.io.StringWriter;
import java.util.Arrays;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
DogDTO dog = new DogDTO();
dog.setCode("001");
dog.setName("chihuahua");
CatDTO cat = new CatDTO();
cat.setCode("002");
cat.setName("felix");
Zoo zoo = new Zoo();
zoo.setAnimals(new AnimalDTO[] { dog, cat });
Data data = new Data();
data.setZoo(zoo);
ObjectMapper objectMapper = new ObjectMapper();
StringWriter writer = new StringWriter();
objectMapper.writeValue(writer, data);
System.out.println(writer);
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
abstract class AnimalDTO {
private String code;
private String name;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "AnimalDTO [code=" + code + ", name=" + name + "]";
}
}
class DogDTO extends AnimalDTO {
}
class CatDTO extends AnimalDTO {
}
class Zoo {
#JsonProperty(value = "Animals")
private AnimalDTO[] animals;
public AnimalDTO[] getAnimals() {
return animals;
}
public void setAnimals(AnimalDTO[] animals) {
this.animals = animals;
}
#Override
public String toString() {
return "Zoo [animals=" + Arrays.toString(animals) + "]";
}
}
class Data {
#JsonProperty(value = "Zoo")
private Zoo zoo;
public Zoo getZoo() {
return zoo;
}
public void setZoo(Zoo zoo) {
this.zoo = zoo;
}
#Override
public String toString() {
return "Data [zoo=" + zoo + "]";
}
}
This program prints:
{"Zoo":{"Animals":[{"type":"DogDTO","code":"001","name":"chihuahua"},{"type":"CatDTO","code":"002","name":"felix"}]}}