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.
Related
I have following Incoming json, which I deserialize to Model.java and then copy that java object to ModelView.java. I wan't to convert date from String to milliseconds and send the Outgoing json as response.
How do I go for it ?
I've specific reason to copy the value from Model.java to ModelView.java using object mapper. So please don't suggest to modify that part. I'm looking to do this via annotation. I'm pretty sure that it can be done, but don't know how.
The json provided here is a simplistic one. I have a large json in actual scenario.
Incoming json
{
"date":"2016-03-31"
}
Outgoing Json
{
"date":236484625196
}
My Controller Class
#Controller
public class SomeController extends BaseController {
#Autowired
private SomeService someService;
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
public #ResponseBody
ResponseEntity<RestResponse> getDetails(HttpServletRequest request, HttpServletResponse response) {
Model model = someService.getData();
ModelView modelView = ModelView.valueOf(model);
return getSuccessResponse(modelView);
}
}
Model.java
#JsonInclude(Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Model implements Serializable {
private String date;
//normal getters and setters
}
ModelView.java
#JsonInclude(Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class ModelView implements Serializable {
private Long date;
//normal getters and setters
public static ModelView valueOf(Model model){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
ObjectMapper mapper = new ObjectMapper();
ModelView modelView = mapper.convertValue(model, ModelView.class);
try {
modelView.setDate(sdf.parse(model.getDate()).getTime());
} catch (ParseException e) {
IntLogger.error("logging error here");
}
return modelView;
}
}
I'm open to change the variable name from "date" to something else in ModelView.java but the outgoing json should remain same.
Jackson has some build in date formatting, for example, you can set the DateFormatter on the object mapper, but i believe this only works if the serialization and deserialization format is the same.
A simpler approach to date serialization and deserialization, if you want serialization and deserialization to be different format, is to use #JsonSerialize and #JsonDeserialize annotations on your Model.class directly (this could obsolete the need for ModelView if your only purpose was to convert the date).
You can create two classes for serialization and deserialization:
public class JsonDateDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String dateString = jsonParser.getText();
try {
return dateFormat.parse(dateString);
}
catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
Then for the serialization to your Outgoing json:
public class JsonDateSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(Long.toString(date.getTime()));
}
}
Now, you an just annotate your Model.java:
#JsonInclude(Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Model implements Serializable {
#JsonSerialize(using = JsonDateSerializer.class)
#JsonDeserialize(using = JsonDateDeserializer.class)
private String date;
//normal getters and setters
}
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
How to include class name in all serialized objects? E.g. adding "_class: 'MyClass'" to output value. Is there some global setting for that? I don't want to add any annotation to pojo classes.
I'm using it with spring4 webmvc #ResponseBody (only json format).
You need to annotated your type with the #JsonTypeInfo annotation and configure how the type information should be serialized. Refer this page for reference.
Example:
public class JacksonClassInfo {
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "__class")
public static class Bean {
public final String field;
#JsonCreator
public Bean(#JsonProperty("field") String field) {
this.field = field;
}
#Override
public String toString() {
return "Bean{" +
"field='" + field + '\'' +
'}';
}
}
public static void main(String[] args) throws IOException {
Bean bean = new Bean("value");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(bean);
System.out.println(json);
System.out.println(mapper.readValue(json, Bean.class));
}
}
Output:
{
"__class" : "stackoverflow.JacksonClassInfo$Bean",
"field" : "value"
}
Bean{field='value'}
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'}}
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"}]}}