Jersey not working for POST, PUT - java

I have a problem which seems to have a simple solution but I haven't been able to solve for months:
I'm trying to POST/PUT JSONs, and having the server recognize and deserialize them as their corresponding classes. But the server is not able to parse the entity, and returns a 500 error code.
This has to do with the fact that the generated code expects an interface (Apple) instead of a 'normal' Java class (AppleImpl).
I know this because the methods work if I change the interface (Apple) with the implementation (AppleImpl) in the endpoint, since the server detects the type and deserializes it correctly, but it is tedious and error-prone to manually change the interfaces to implementations every time that a change is made in the RAML.
In the Java backend, I'm using an entity, interface and endpoint, all of which were generated using raml-to-jax-rs v3.0.2. I'm using Maven with Jersey (jersey-bom) v2.27.
To summarize, I would like to know how to successfully deserialize an interface in Jersey/JAX-RS. Am I missing an annotation, are my Maven package versions wrong, or is it something else?
I would very much appreciate if someone could help me with this, and I will provide more information if necessary. Below is some sample generated code so that the problem is clearer.
Sample generated code:
(1/3) Sample generated interface Apple:
#JsonDeserialize(
as = AppleImpl.class
)
public interface Apple {
#JsonAnyGetter
Map<String, Object> getAdditionalProperties();
#JsonAnySetter
void setAdditionalProperties(String key, Object value);
#JsonProperty("name")
String getName();
#JsonProperty("name")
void setName(String name);
#JsonProperty("values")
List<String> getValues();
#JsonProperty("values")
void setValues(List<String> values);
#JsonProperty("defaultValue")
String getDefaultValue();
#JsonProperty("defaultValue")
void setDefaultValue(String defaultValue);
}
(2/3) Sample generated class AppleImpl:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"name",
"values",
"defaultValue"
})
public class AppleImpl implements Apple {
#NotNull
#JsonProperty("name")
private String name;
#JsonProperty("values")
#NotNull
private List<String> values;
#JsonProperty("defaultValue")
#NotNull
private String defaultValue;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("name")
public String getName() {
return this.name;
}
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
#JsonProperty("values")
public List<String> getValues() {
return this.values;
}
#JsonProperty("values")
public void setValues(List<String> values) {
this.values = values;
}
#JsonProperty("defaultValue")
public String getDefaultValue() {
return this.defaultValue;
}
#JsonProperty("defaultValue")
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperties(String key, Object value) {
this.additionalProperties.put(key, value);
}
#Override
public boolean equals(Object o) {
if (o == null) return false;
if (this == o) return true;
if (getClass() != o.getClass()) return false;
AppleImpl other = (AppleImpl) o;
return java.util.Objects.equals(this.name, other.name) && java.util.Objects.equals(this.values, other.values) && java.util.Objects.equals(this.defaultValue, other.defaultValue) && java.util.Objects.equals(this.additionalProperties, other.additionalProperties);
}
#Override
public int hashCode() {
return Objects.hash(name,values,defaultValue,additionalProperties);
}
}
(3/3) Sample generated POST endpoint:
#Override
public PostApplesResponse postApples(String xSessionToken, Apple bean) {
try {
MUser user = AuthUtils.authenticates(xSessionToken);
ModelMapperApple.get().validateBean(bean);
MApple entity = ModelMapperApple.get().b2e(bean);
MApple entityOut = AppleService.getInstance().post(user, entity);
AppleResponse beanOut = ModelMapperApple.get().e2b(entityOut);
return PostApplesResponse.respond201WithApplicationJson(beanOut);
} catch (BadRequestException e) {
CRUDUtils.logException(e);
return PostApplesResponse.respond400WithApplicationJson(ErrorMessageUtils.build(e));
} catch (UnauthorizedException e) {
CRUDUtils.logException(e);
return PostApplesResponse.respond401();
} catch (ConflictException e) {
CRUDUtils.logException(e);
return PostApplesResponse.respond409();
} catch (Exception e) {
CRUDUtils.logException(e);
return PostApplesResponse.respond500();
}
}

Related

Spring Enum list empty in #RequestBody

I have the below enum with two values, and i have a search api with many fields, One of these fields is a list of StatusEnum. So i created a dto that contains this field.
The problem when i send the data the list status is always empty
json exp: {"status":["En activité"],"startDate":null,"endDate":null}
public enum StatusEnum {
INACTIVITY, ENDACTIVITY;
private static Map<String, StatusEnum > namesMap = new HashMap<>(2);
static {
namesMap.put("En activité", INACTIVITY);
namesMap.put("En fin d'activité", ENDACTIVITY);
}
#JsonCreator
public static StatusEnum forValue(String value) {
return namesMap.get(StringUtils.lowerCase(value));
}
#JsonValue
public String toValue() {
for (Entry<String, StatusEnum > entry : namesMap.entrySet()) {
if (entry.getValue() == this)
return entry.getKey();
}
return null;
}
}
#PostMapping
public ResponseEntity<List<Object>> search(#RequestBody SearchDTO search) { }
public class SearchDTO {
private Date startDate;
private Date endDate
private List<StatusEnum> status;
//getter and setter
}
#JsonCreator
public static StatusEnum forValue(String value) {
return namesMap.get(StringUtils.lowerCase(value));
}
Problem is the usage of #lowerCase in forValue!
Your keys in your map aren't lower-cased. That's why namesMap.get can't find anything.

Jackson serializes list of objects with empty objects

I'm trying to serialize a very large object graph that uses #JsonView (I don't know if that's relevant or not). What I'm finding is that for a list of objects within the graph, Jackson is serializing a list of empty objects, such as [{},{},{}]. None of the attributes are in the string. All scalar attributes serialize just fine. I'm only having trouble with the lists.
I've verified several times that the attributes are being set in the objects. Part of my POJO looks like so:
public class ProfessionalData implements Serializable {
#JsonProperty("collegeEducation")
#JsonView(Views.Myview.class)
#Valid
private List<CollegeEducation> collegeEducation = new ArrayList<CollegeEducation>();
#JsonProperty("managementCommittee")
#JsonView(Views.Myview.class)
#Valid
private List<ManagementCommittee> managementCommittee = new ArrayList<ManagementCommittee>();
//getters and setters
}
This is the ObjectMapper code:
#Override
public String convertToDatabaseColumn(#NotNull MyPojoItem item) {
try {
//Disable Default_view_Inclusion, so fields without a view annotation wont be included
objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
objectMapper.setDateFormat(df); //Transform the Object to String
String result = objectMapper
.writerWithView(Views.MyView.class)
.writeValueAsString(item);
return result;
} catch (Exception ex) {
LOG.error("Failed to convertToDatabaseColumn: " + ex.getMessage());
return null;
}
}
Can someone tell me what this usually means?
You could do something like this for both lists. A custom serializer to write your objects there from a List. For example, for the CollegeEducation list:
public class CollegeEducationSerializer extends JsonSerializer<List<CollegeEducation>>
{
#Override
public void serialize(List<CollegeEducation> list, JsonGenerator json,
SerializerProvider provider) throws IOException
{
if (list == null || list.isEmpty())
return;
json.writeFieldName("CollegeEducationArray");
json.writeStartArray();
for (CollegeEducation ce : list)
{
json.writeStartObject();
json.writeObjectField("CollegeEducation", ce);
json.writeEndObject();
}
json.writeEndArray();
}
}
And specify it on the annotations:
#JsonSerialize(using = CollegeEducationSerializer.class)
private List<CollegeEducation> collegeEducation = new ArrayList<CollegeEducation>();
In order to read it, the same logic applies (a custom deserializer for both lists).
For something like a generic approach, you could also do something like this:
public class CustomListSerializer extends JsonSerializer<List<? extends ListElement>>
{
#Override
public void serialize(List<? extends ListElement> list, JsonGenerator json,
SerializerProvider provider) throws IOException
{
if (list == null || list.isEmpty())
return;
json.writeFieldName(list.get(0).getElementType()+"Array");
json.writeStartArray();
for (ListElement le : list)
{
json.writeStartObject();
json.writeObjectField(le.getElementType(), le);
json.writeEndObject();
}
json.writeEndArray();
}
}
Then you could use it for all your Lists:
#JsonSerialize(using = CustomListSerializer.class)
private List<CollegeEducation> collegeEducation = new ArrayList<>();
#JsonSerialize(using = CustomListSerializer.class)
private List<ManagementCommittee> managementCommittee = new ArrayList<>();
For this I added a customProperties Map, which may be prone to errors here. So the approach would be writing manually the desired fields, instead of using json.writeObjectField(le.getElementType(), le);
public abstract class ListElement
{
public abstract String getElementType();
public Map<String, String> properties = new HashMap<>();
public final String name;
public final int id;
public ListElement(String name, int id)
{
this.name = name;
this.id = id;
}
}
and, f.e:
public class CollegeEducation extends ListElement
{
protected String location;
protected String director;
public CollegeEducation(String name, int id, String location, String director)
{
super(name,id);
this.director = director;
this.location= location;
properties.put("director", director);
properties.put("location", location);
//...
}
public String getElementType()
{
return "CollegeEducation";
}
//...
}
//...
In the serializer:
json.writeFieldName(list.get(0).getElementType()+"Array");
json.writeStartArray();
for (ListElement le : list)
{
json.writeStartObject();
json.writeFieldName(le.getElementType()+"-"+le.id);
json.writeStartObject();
json.writeStringField("name", le.name);
for(Map.Entry<String,String> kv : le.properties.entrySet())
json.writeStringField(kv.getKey(), kv.getValue());
json.writeEndObject();
json.writeEndObject();
}
json.writeEndArray();
You should get something like:
{
"CollegeEducationArray":
[
{ "CollegeEducation-22" :
{
"name" : "Lauaxeta",
"director" : "AJerk",
"location": "Bilbao"
}
},
{ "CollegeEducation-55" :
{
"name" : "Harvard",
"director" : "OtherJerk",
"location": "Rwanda"
}
}
]
}

Unable to make restTemplate call with Generics for nested Json

I am trying to make a restTemplate call for API testing. The json returned is a nested one with multiple levels.
{
"code": 200,
"data": {
"result": {
"publicId": "xyz"
}
}
}
I have the following classes acting as wrapper :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public abstract class RestCallResponse<T> {
private int code;
protected RestCallResponse(int code) {
this.code = code;
}
protected RestCallResponse(){}
#JsonProperty("data")
public Map<?, ?> getRestCallResponse() {
return ImmutableMap.of("result", getResult());
}
#JsonIgnore
protected abstract T getResult();
public int getCode() {
return code;
}
}
And then a SuccessRestResponse class extending this abstract class :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class SuccessRestResponse<T> extends RestCallResponse<T> {
#JsonProperty("result")
private T result;
public SuccessRestResponse() {
}
public SuccessRestResponse(T result) {
super(HttpStatus.SC_OK);
this.result = result;
}
protected T getResult() {
return this.result;
}
}
Then finally I have the actual data POJO :
public final class CreatedResponse {
#JsonProperty
private final EntityId publicId;
public CreateCreativeResponse(EntityId publicId) {
this.publicId = publicId;
}
}
In the test case, I am making a call as such :
ResponseEntity<SuccessRestResponse<CreatedResponse>> newResponse =
restTemplate.exchange(requestEntity, new ParameterizedTypeReference<SuccessRestResponse<CreatedResponse>>() {});
But I am getting the following error :
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: null value in entry: result=null (through reference chain: com.inmobi.helix.test.api.dao.SuccessRestResponse["data"]);
Any suggestions? Where am I going wrong?
I solved the problem with a workaround. Still don't know what's wrong with the above piece of code though.
I got rid of the class RestCallResponse<T> and edited the field members in SuccessRestResponse<T> to look like this :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class SuccessRestResponse<T> {
private int code;
private Map<String, T> data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public Map<String, T> getData() {
return data;
}
#JsonIgnore
public T getResult() {
return data.get("result");
}
public void setData(Map<String, T> data) {
this.data = data;
}
}
This corresponds to the nested json while deserialization.
P.S. - Would still like to know what went wrong in my above code
though. As in, why did class hierarchy not work for me.

How to serialize java objects as field.path = field.value

I have the following model classes:
package com.ab.model;
import java.util.List;
public class Request {
public Request(String requestType, Body body, List<String> emails) {
this.requestType = requestType;
this.body =body;
this.emails = emails;
}
private String requestType;
private Body body;
private List<String> emails;
public String getRequestType() {
return requestType;
}
public void setRequestType(String requestType) {
this.requestType = requestType;
}
public Body getBody() {
return body;
}
public void setBody(Body body) {
this.body = body;
}
public List<String> getEmails() {
return emails;
}
public void setEmails(List<String> emails) {
this.emails = emails;
}
}
class Body {
private String content;
private List<Header> headers;
public Body(String content, List<Header> headers) {
this.content = content;
this.headers = headers;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<Header> getHeaders() {
return headers;
}
public void setHeaders(List<Header> headers) {
this.headers = headers;
}
}
class Header {
private String headerName;
public Header (String headerName) {
this.headerName = headerName;
}
public String getHeaderName() {
return headerName;
}
public void setHeaderName(String headerName) {
this.headerName = headerName;
}
}
And the following instance of the Request class:
Request request = new Request(
"get",
new Body("abcdefg",
Arrays.asList(new Header("header_one"))),
Arrays.asList("a#a.com", "b#b.com"));
Do you know any library or algorithm that can serialize the request object into the following string?
requestType = "get"
body.content = "abcdefg"
body.headers[0].headerName = "header_one"
emails[0] = "a#a.com"
emails[1] = "b#b.com"
I know I can serialize it as json, xml, etc, but these don't fit my use case.
Basically I need a serialization like:
field.nestedField.reallyNestedField = "its primitive value"
As a next step, I am planning to read the generated string and generate arbitrary data for each field/nestedField then deserialize it back using PropertyUtils from Apache e.g.:
PropertyUtils.setProperty(requestObject, "requestType", "random type");
PropertyUtils.setProperty(requestObject, "body.content", "random content");
//...
Many thanks!
Andrei
What about overriding your toString() default methods to read and output your member variables as text. You can use super to refer to your SuperClass and it's members.
PS: You don't have default constructors in your classes! In case you have constructors with your argument list it is suggested to include your no-argument default constructor in your class! Especially in case you are implementing some logic related to serialisation / deserialisation!
You can iterate and recurse on the class/object properties using Commons PropertyUtils.
Depending on how complex your implementation is, you might need to do some type checking for primitive/wrapper/collection types (the below leverages Commons ClassUtils).
public static List<String> getPropertyDescriptorPaths(Class<?> clazz) {
return getPropertyDescriptorPaths("", clazz);
}
private static List<String> getPropertyDescriptorPaths(String prefix, Class<?> clazz) {
List<String> paths = new ArrayList<>();
PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(clazz);
for (PropertyDescriptor pd : descriptors) {
if (isSimpleType(pd.getPropertyType())) {
paths.add(prefix + pd.getName());
} else if (!pd.getName().equals("class")) {
paths.addAll(getPropertyDescriptorPaths(pd.getName() + ".", pd.getPropertyType()));
}
}
return paths;
}
private static boolean isSimpleType(Class<?> clazz) {
return ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.equals(String.class) || isCollectionOrArray(clazz);
}
private static boolean isCollectionOrArray(Class<?> clazz) {
return isCollection(clazz) || clazz.isArray();
}
private static final List<Class<?>> COLLECTION_TYPES = Arrays.asList(new Class<?>[] { List.class, Map.class, Set.class });
private static boolean isCollection(Class<?> clazz) {
for (Class<?> eachClass : COLLECTION_TYPES) {
if (eachClass.isAssignableFrom(clazz)) {
return true;
}
}
return false;
}
The condition for comparing property name to class is because each object has a getClass() method, and we don't care about that.
Using this with your classes, we get the result:
System.out.println(getPropertyDescriptorPaths(Request.class));
// [emails, requestType, body.headers, body.content]

Json to Object using Gson

I have a class DocumentBO which has the following attributes -
public class DocumentBO implements IStorageBO {
private String aId;
private String studyId;
private Map<AlgorithmsEnum, JobIOStatus> status;
private String text;
private Collection<Sentence> sentences;
public String getaId() {
return aId;
}
public void setaId(String aId) {
this.aId = aId;
}
public String getStudyId() {
return studyId;
}
public void setStudyId(String studyId) {
this.studyId = studyId;
}
public Map<AlgorithmsEnum, JobIOStatus> getStatus() {
return status;
}
public void setStatus(Map<AlgorithmsEnum, JobIOStatus> status) {
this.status = status;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Collection<Sentence> getSentences() {
return sentences;
}
public void setSentences(Collection<Sentence> sentences) {
this.sentences = sentences;
}
}
The AlgorithmsEnum is as follows -
public enum AlgorithmsEnum {
SENTIMENT("sentiment"),
INTENTION("intention"),
TOPIC("topic"),
NER("ner"),
UIMA("uima");
private final String value;
private AlgorithmsEnum(String value) {
this.value = value;
}
public String value() {
return value;
}
#Override
public String toString() {
return value;
}
public static AlgorithmsEnum fromValue(String value) {
if (value != null) {
for (AlgorithmsEnum aEnum : AlgorithmsEnum.values()) {
if (aEnum.value().equals(value)) {
return aEnum;
}
}
}
return null;
}
}
The JobIOStatus is also similar.
I am successfully able to create a JSON string of Collection using GSON using the following TypeToken
Type type = new TypeToken<Collection<DocumentBO>>() {}.getType();
But, when I try to recreate the Collection object using the JSON string returned by Gson and the same TypeToken, the key of the status hashmap is always returned as NULL whereas the value is successfully created. What do you think can be the issue?
The problem is that you have overridden toString() in your enum.
If you look at the JSON being produced, the keys to your Map<AlgorithmsEnum, JobIOStatus> are the lowercase names you're creating. That won't work. Gson has no idea how to recreate the enum from those when you attempt to deserialize the JSON.
If you remove your toString() method it will work just fine.
Alternatively you can use the .enableComplexMapKeySerialization() method in GsonBuilder when serializing which will ignore your toString() method and produce JSON using the default representations of your enum values which is what is required.
There are "well" known :) issues of Gson to serialize Map when the key is derived from object and its not a "native" data type.
Please use this
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.enableComplexMapKeySerialization().create();
Collection<DocumentBO> obj = gson.fromJson(str, type);

Categories