jsonb: nested serializing not called by Jsonb - java

New Tag request: java-ee-8
It's got a new feature, called jsonb. With jsonb, I cannot get nested serialization working. See bold printed below.
So, I wrote a jaxrs-application. This application's got a messagebodywriter using jsonb:
final JsonbConfig defaultConfig = new JsonbConfig()
.withFormatting(Boolean.TRUE)
.withNullValues(Boolean.TRUE)
.withSerializers(
new QueryParamEntrySerializer(),
new ApiResponseDtoSerializer())
.withAdapters(new ResponseStatusJsonbAdapter());
final Jsonb jsonb = JsonbBuilder.create(defaultConfig);
ApiResponseDto is like following:
#Value.Immutable
#JsonbTypeSerializer(ApiResponseDtoSerializer.class)
public interface ApiResponseDto {
ResponseStatus status();
String message();
Optional<? extends Object> data();
}
ResponseStatus is an enumm and gets serialized via the above TypeAdapter just fine.
For this class I wrote the ApiResponseDtoSerializer.
#Provider
public class ApiResponseDtoSerializer implements JsonbSerializer<ImmutableApiResponseDto> {
#Override
public void serialize(
final ImmutableApiResponseDto obj,
final JsonGenerator generator,
final SerializationContext ctx) {
generator.writeStartObject();
ctx.serialize("status", obj.status(), generator);
ctx.serialize("data", obj.data(), generator);
ctx.serialize("message", obj.message(), generator);
generator.writeEnd();
}
}
Now the Optional data() shall contain an ImmutableSet of QueryParamEntry like this:
#Value.Immutable
#JsonbTypeSerializer(ImmutableQueryParamEntrySerializer.class)
public interface QueryParamEntry {
#Value.Parameter
String key();
#Value.Parameter
Optional<String> value();
}
The type adapter is this one:
#Provider
public class ImmutableQueryParamEntrySerializer implements JsonbSerializer<ImmutableQueryParamEntry> {
private static final Logger LOG = LoggerFactory.getLogger(ImmutableQueryParamEntrySerializer.class);
#Override
public void serialize(
final ImmutableQueryParamEntry obj,
final JsonGenerator generator,
final SerializationContext ctx) {
generator.writeStartObject();
LOG.debug("Writing: key = [{}].", obj.key());
ctx.serialize("key", obj.key(), generator);
ctx.serialize("value", obj.value(), generator);
generator.writeEnd();
}
}
The final output is:
{
"status": "success",
"data": [
{
"key": null,
"value": null
}
],
"message": "Returning query param values."
}
As you can see, the nested serialization did not work. Jsonb seems to find the correct type (because otherwise it wouldn't serialize an object at all). But even the log statement from my SerializerClass is never called.
Btw: You need Guava 22 and immutables.github.io to compile this code, and slf4j obviously:
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
<version>3.0.2.Final</version>
<scope>provided</scope>
</dependency>
<!-- JSON-P API -->
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.json.bind/javax.json.bind-api -->
<dependency>
<groupId>javax.json.bind</groupId>
<artifactId>javax.json.bind-api</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>

So here is what it takes to make it work.
I got rid of the custom Serializers. As mentioned in my comment, they are broken before the unreleased version 1.0.3 anyway.
Instead, rename your methods to getStatus(), getMessage() and getData() (notice the get-Prefix).
For getData();, return just an Optional<Object>, not Optional<? extends Object>. Otherwise, immutables will refuse the special treatment of Optional.
After that, all just worked nicely.

Related

Parsing Yaml in Java

I have the following YAML I want to parse using Jackson parser in Java.
android:
"7.0":
- nexus
- S8
"6.0":
- s7
- g5
ios:
"10.0":
- iphone 7
- iphone 8
I created a created class which has getter and setter as Java Object for android. It works fine. But how do I do the same for 6.0 and 7.0? I'm usingJackson` Parser
No idea whether Jackson supports that; here's a solution with plain SnakeYaml (I will never understand why people use Jackson for parsing YAML when all it does is basically take away the detailed configuration possible with SnakeYaml which it uses as backend):
class AndroidValues {
// showing what needs to be done for "7.0". "8.0" works similarly.
private List<String> v7_0;
public List<String> getValuesFor7_0() {
return v7_0;
}
public void setValuesFor7_0(List<String> value) {
v7_0 = value;
}
}
// ... in your loading code:
Constructor constructor = new Constructor(YourRoot.class);
TypeDescription androidDesc = new TypeDescription(AndroidValues.class);
androidDesc.substituteProperty("7.0", List.class, "getValuesFor7_0", "setValuesFor7_0");
androidDesc.putListPropertyType("7.0", String.class);
constructor.addTypeDescription(androidDesc);
Yaml yaml = new Yaml(constructor);
// and then load the root type with it
Note: Code has not been tested.
I think that you should try annotation com.fasterxml.jackson.annotation.JsonProperty. I'll provide a short example below.
Sample YAML file:
---
"42": "some value"
Data transfer object class:
public class Entity {
#JsonProperty("42")
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Parser:
public class Parser {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
Entity entity = mapper.readValue(new File("src/main/resources/sample.yml"), Entity.class);
System.out.println(entity.getValue());
}
}
The console output should be: some value.
P.S. I tested it with the following dependencies:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.3</version>
</dependency>

jackson - jackson not creating json for nested objects

I have a user list class and an API that returns user list and total records.
The class is as follows :
#JsonInclude(JsonInclude.Include.NON_NULL)
public class FMSResponseInfo {
#JsonProperty("status")
private String status;
#JsonProperty("message")
private String message;
#JsonProperty("data")
private Object data;
#JsonProperty("status")
public String getStatus() {
return status;
}
#JsonProperty("status")
public void setStatus(String status) {
this.status = status;
}
#JsonProperty("message")
public String getMessage() {
return message;
}
#JsonProperty("message")
public void setMessage(String message) {
this.message = message;
}
#JsonProperty("data")
public Object getData() {
return data;
}
#JsonProperty("data")
public void setData(Object data) {
this.data = data;
}
}
#JsonInclude(JsonInclude.Include.NON_NULL)
public class UserListResDTO {
#JsonProperty("users")
private List<UserDTO> users;
#JsonProperty("totalRecords")
private long totalRecords;
public List<UserDTO> getUsers() {
return users;
}
public void setUsers(List<UserDTO> users) {
this.users = users;
}
public long getTotalRecords() {
return totalRecords;
}
public void setTotalRecords(long totalRecords) {
this.totalRecords = totalRecords;
}
}
I am setting an object of type UserListResDTO in FMSResponseInfo as shown below.
I have been successful in creating web services and returning response as json, so far. But the problem I am facing is that the API returns the response as follows :
{"data":"org.package.UserListResDTO#70783307","message":"Success","status":"200"}
And this is how I have written the web service :
#Path("/getUsers")
#GET
#Produces(MediaType.APPLICATION_JSON)
public FMSResponseInfo getUsers(#QueryParam("page") #DefaultValue("0") int page) {
System.out.println("In getUsers()");
FMSResponseInfo fmsResponseInfo = new FMSResponseInfo();
try {
UserListResDTO userList = fmsUserManager.getAllUsers(page);
fmsResponseInfo.setData(userList);
fmsResponseInfo.setStatus(FMSErrorMessageEnum.SUCCESS_CODE.getValue());
fmsResponseInfo.setMessage(FMSErrorMessageEnum.SUCCESS_MESSAGE.getValue());
} catch (Exception e) {
return FMSUtil.getErrorResponseInfo(FMSErrorMessageEnum.INTERNAL_SERVER_ERROR_CODE.getValue(),
e.getMessage());
}
System.out.println("Out getUsers()");
return fmsResponseInfo;
}
I guess there is some problem with the dependencies or something that I am unable to resolve. Major dependencies in my pom are :
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>
and I am creating Web services by extending Application class as follow :
#ApplicationPath("rest")
public class FMSApplication extends Application {
public Set<Class<?>> getClasses(){
Set<Class<?>> set = new HashSet<Class<?>>();
set.add(FMSUserManagerWebService.class);
set.add(FMSDocumentManagerWebService.class);
set.add(FMSInboxManagerWebService.class);
set.add(FMSLocationManagerWebService.class);
return set;
}
}
Any help will be really appreciated as I am new to this REST web services and have been stuck for quite long.
This link will explain the answer
https://jersey.java.net/documentation/latest/media.html#d0e7963
9.1.1.1. POJO support
POJO support represents the easiest way to convert your Java Objects
to JSON and back.
Media modules that support this approach are MOXy and Jackson
The link to Jackson includes
9.1.4.1. Dependency
To use Jackson 2.x as your JSON provider you need to add
jersey-media-json-jackson module to your pom.xml file
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.2</version>
</dependency>
I don't know much about moxy but you had jackson on your CLASSPATH and were using Jackson annonations. Jersey however was configured to use moxy.
From link
JSON binding support via MOXy is a default and preferred way of
supporting JSON binding in your Jersey applications since Jersey 2.0.
When JSON MOXy module is on the class-path, Jersey will automatically
discover the module and seamlessly enable JSON binding support via
MOXy in your applications.
MOXy seemed to have handled FMSResponseInfo. Why it didn't handle the other object I do not know. But since you wanted to use Jackson you should have been using the Jackson module.
As suggested by Shire Resident in the comments using the following dependency I was able to resolve the problem :
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.22.2</version>
</dependency>

Jersey + Jetty + JSON

I would like to produce an JSON using Jetty + Jersey.
My POM.XML is similar to this post: How do I update example to work with latest versions Jetty (9.1.0.RC2) and Jersey (2.7)?. I imagine that i am missing some dependecy. The result from inferFile() is returning blank.
I can see that the method toStirng from Student class was not been called.
Maven
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.1.3.v20140225</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.1.3.v20140225</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>2.14</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>2.14</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-jetty-http</artifactId>
<version>2.14</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.14</version>
</dependency>
Java
public class Student {
public Student(){
}
#Override
public String toString(){
return new StringBuffer(" First Name : ").append("MY FIRST NAME").toString();
}
}
#Path("/bulkload")
public class BulkLoadAPI {
#POST
#Path("inference")
#Consumes(MediaType.TEXT_PLAIN)
#Produces(MediaType.APPLICATION_JSON)
public Student inferFile() throws URISyntaxException, IOException {
Student s = new Student();
return s;
}
}
public static void main(String[] args) throws Exception {
ServletHolder jerseyServlet = new ServletHolder(ServletContainer.class);
jerseyServlet.setInitParameter("jersey.config.server.provider.classnames", "service.api.BulkLoadAPI");
jerseyServlet.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
Server server = new Server(10500);
ServletContextHandler context = new ServletContextHandler (server, "/", Servl etContextHandler.SESSIONS);
context.addServlet(jerseyServlet, "/*");
server.start();
server.join();
}
I'm not really sure what you're expecting. If you're expecting toString() to be called (which it isn't), that wouldn't even produce valid JSON. The way POJO to JSON (and vice versa) conversion is done is through MessageBodyReaders and MessageBodyWriters. Basically they are looking for fields with either some form of annotation known to the marshaller/unmarshaller, or Java bean style getters and setters. That's how the data/properties for the JSON will be discovered.
For example, if your Student looked like
public class Student {
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
it would give you JSON like {"firstName":"MyFirstName"}. You just need to set the property
public Student inferFile() throws URISyntaxException, IOException {
Student s = new Student();
s.setFirstName("MyFirstNaem");
return s;
}
Another thing, this is not needed
setInitParameter("com.sun.jersey.api.json.POJOMappingFeature","true");
That is a Jersey 1 feature. You are using Jersey 2. See also
jersey.config.server.provider.packages
So you don't have to configure each class individually. The value should be a package. It will scan the package and sub packages for annotated classes.

java.util.Date in Spring Data Rest

I'm using a java.util.Date in spring(3.1) data REST. How can I get the date to print in a human readable form? (e.g. MM/DD/YYYY)?
#Entity
public class MyEntity{
...
#Column(name="A_DATE_COLUMN")
#DateTimeFormat(iso=ISO.DATE)
private Date aDate;
..getters and setters
}
However when i print my entity(after overriding toString), I'm always getting the date as a long. It seems like #DateTimeFormat does not change the behaviour. I also tried different iso formats and that didnt help either.
"aDate" : 1320130800000
Here is my POM file entry for the spring data rest
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
<version>1.0.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId></groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.1</version>
</dependency>
Any help is much appeciated.
PS. Here is the toString Implementation
#Override
public String toString() {
return getClass().getName() + "{"+
"\n\taDate: " + aDate
+ "\n}";
}
looks like you will need to write a custom serializer to make Jackson (the JSON library spring uses under the hood) properly serialize the date out to text.
your getter will then look like this (where JsonDateSerializer is the custom class)
#JsonSerialize(using=JsonDateSerializer.class)
public Date getDate() {
return date;
}
check out this blog post that includes code for the serializer. The serializer code is replicated here, but the explanation in the blog post may help.
/**
* Used to serialize Java.util.Date, which is not a common JSON
* type, so we have to create a custom serialize method;.
*/
#Component
public class JsonDateSerializer extends JsonSerializer<Date>{
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}

PropertyEditor is not called on AJAX (JSON) request

I have problem with Ajax request on form submit. The form contains these stringify JSON data:
{"articleContent":"<p>aaa</p>","title":"Po vyplnění titulku aktuality budete","header":"aa","enabled":false,"timestamp":"1358610697521","publishedSince":"03.01.2013 00:00","publishedUntil":"","id":"10"}
When json contains "03.01.2013 00:00" value, server respons is 400 Bad Request
Problem is that, custom DateTimePropertyEditor (which is registrated with #InitBinder) is not called, and DateTime in String format is not conveted. Have you any idea How to solve this problem?
Controllers mapped method, which is processing request
#RequestMapping( value = "/admin/article/edit/{articleId}", method = RequestMethod.POST, headers = {"content-type=application/json"})
public #ResponseBody JsonResponse processAjaxUpdate(#RequestBody Article article, #PathVariable Long articleId){
JsonResponse response = new JsonResponse();
Article persistedArticle = articleService.getArticleById(articleId);
if(persistedArticle == null){
return response;
}
List<String> errors = articleValidator.validate(article, persistedArticle);
if(errors.size() == 0){
updateArticle(article, persistedArticle);
response.setStatus(JsonStatus.SUCCESS);
response.setResult(persistedArticle.getChanged().getMillis());
}else{
response.setResult(errors);
}
return response;
}
InitBinder
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(DateTime.class, this.dateTimeEditor);
}
I solved this problem with using #JsonDeserialize
#JsonDeserialize(using=DateTimeDeserializer.class)
public DateTime getPublishedUntil() {
return publishedUntil;
}
I have to implemetd custom Deserializer.
public class DateTimeDeserializer extends StdDeserializer<DateTime> {
private DateTimeFormatter formatter = DateTimeFormat.forPattern(Constants.DATE_TIME_FORMAT);
public DateTimeDeserializer(){
super(DateTime.class);
}
#Override
public DateTime deserialize(JsonParser json, DeserializationContext context) throws IOException, JsonProcessingException {
try {
if(StringUtils.isBlank(json.getText())){
return null;
}
return formatter.parseDateTime(json.getText());
} catch (ParseException e) {
return null;
}
}
}
This is not handled by a Property Editor - which acts on form fields and not on json bodies. To handle a non-standard date format in a json, you will have to customize the underlying ObjectMapper. Assuming you are using jackson 2.0+, these are what you can do:
a. Tag the publishedSince field with an annotation that tells Object mapper the format for date - based on instructions here:
public class Article{
...
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="MM.dd.yyyy HH:mm")
private Date publishedSince;
}
b. Or second option is to modify the ObjectMapper itself, this could be global though, so may not work for you:
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper(){
super.setDateFormat(new SimpleDateFormat("MM.dd.yyyy hh:mm"));
}
}
and configure this with Spring MVC:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="..CustomObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
With Spring MVC 4.2.1.RELEASE, you need to use the new Jackson2 dependencies as below for the Deserializer to work.
Dont use this
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.12</version>
</dependency>
Use this instead.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.2</version>
</dependency>
Also use com.fasterxml.jackson.databind.JsonDeserializer and com.fasterxml.jackson.databind.annotation.JsonDeserialize for the deserialization and not the classes from org.codehaus.jackson

Categories