I've seen many questions around using jackson to serialize/deserialize java objects using builder patter, however, I can't figure out why this code below won't work. I'm using Jackson version 2.5.4
#JsonDeserialize(builder = User.Builder.class)
public class User {
private String name;
private User(Builder builder) {
this.name=builder.name;
}
#JsonPOJOBuilder(buildMethodName = "build")
public static class Builder {
private String name;
public Builder name(String name) {
this.name = name;
return this;
}
public User build() {
return new Learner(this);
}
}
}
Trying to output the string representation always prints an empty list {}
By default the #JsonPOJOBuilderexpects the builder methods to starts with with prefix.
You should override this in the annotation: #JsonPOJOBuilder(withPrefix = "")
You should also mark the name field with the #JsonProperty annotation, or add a getter, or use the JacksonFeatureAutoDetect feature; otherwise Jackson does not see name as a JSON property.
Related
I have an enum class as such:
ONE("1", "Description1"),
TWO("2", "Description2");
String value;
String description;
MyEnum(String value, String description) {
this.value = value;
this.description = description;
}
#Override
public String toString() {
return this.value;
}
#JsonValue
public String value() {
return this.value;
}
The API I am interacting with is expecting a param with type String and the values can be comma separated.
For example: api.com/test?param1=1,2
I configured a feign client with the url api.com/test
And then created a POJO like so
public class POJO {
private List<MyEnum> param1;
}
And in my feign client I have:
#RequestMapping(method = RequestMethod.GET)
MyResponse getResponse(#SpringQueryMap POJO request);
Is it possible to somehow turn the List of Enums to a List of String before the API call is made via some Spring approach?
As of right now, when I pass a List of Enums, it is only taking into account the last Enum within this list.
UPDATE: I annotated the property I want to convert to a list using #JsonSerialize(converter=abc.class). However #SpringQueryMap doesn't seem to honor that serialization..
Yes is possible, you need to create an interceptor and in that method do the mapping.
This topic may be for you.
Spring - Execute code before controller's method is invoked
So turns out #JsonSerialize was not working with #SpringQueryMap
So I did have to add an interceptor.
Like so:
public class MyInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate requestTemplate) {
if(requestTemplate.queries().containsKey("param1")) {
requestTemplate.query("param1", convert(requestTemplate.queries().get("param1")));
}
}
//convert list to a string
public String convert(Collection<String> values) {
final String s = String.join(",", values.stream().map(Object::toString).collect(Collectors.toList()));
return s;
}
}
And then in my Feign config class added this:
#Bean
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}
I am passing a request body to a POST request on postman similar to this:
"name":"Mars",
"artifacts":[
{
"elements":[
{
"name":"carbon",
"amount":0.5,
"measurement":"g"
}
],
"typeName":"typeA"
},
{
"elements":[
{
"name":"hydrogen",
"amount":0.2,
"measurement":"g"
}
],
"typeName":"typeB"
}
]
The create method in the rest controller looks like this.
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars;
Planet and all its nested objects have a default constructor such as:
public Planet() {}
However, I am not able to create a new planet object because of lack of a default constructor. Please help!
EDIT:
Planet class
public class Planet {
#JsonProperty("name")
private String name;
#Field("artifacts")
private List<Artifact> artifacts;
public Planet() {}
public Planet(String name, List<Artifact> artifacts)
{
this.name = name;
this.artifacts = artifacts;
}
//setters and getters
}
Artifact class:
public class Artifact() {
#Field("elements")
private List<Element> elements;
#JsonProperty("typeName")
private String typeName;
public Artifact() {}
public Artifact(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
}
Element class:
public class Element() {
#JsonProperty("elementName")
private String name;
#JsonProperty("amount")
private double amount;
#JsonProperty("measurement")
private String measurement;
public Element() {}
public Element(String name, double amount, String measurement)
{
//assignments
}
}
I had that the same error when I forgot the #RequestBody before the parameter
#RequestMapping("/create")
public Planet create(#RequestBody Planet data) {
I don't understand what is the issue you are facing, but i can see an error straight away so guessing that is the issue you are facing, i am going to give you a solution.
Create a class which matches your json data structure like this :
Class PlanetData {
private String name;
private List<Planet> artifacts;
public PlanetData(String name, List<Planet> artifacts){
name = name;
artifacts = artifacts;
}
// include rest of getters and setters here.
}
Then your controller should look like this. Basically you needed to put #RequestBody to all the parameters you want to recieve from request JSON. Earlier you only put #RequestBody to name parameter not artifact parameter and since Request Body can be consumed only once, so you need a wrapper class to recieve the complete request body using single #RequestBody annotation.
#RequestMapping("/create")
public String create(#RequestBody PlanetData data) {
Planet mars = planetService.create(data.getName(),data.getArtifacts());
return mars.toString();
}
Edit : Looking at the Planet class, it also needs some modification
public class Planet {
private String typeName; // key in json should match variable name for proper deserialization or you need to use some jackson annotation to map your json key to your variable name.
private List<Element> elements;
public Planet() {}
public Planet(String typeName, List<Element> elements)
{
this.typeName = typeName;
this.elements = elements;
}
//setters and getters. Remember to change your setters and getter from name to typeName.
}
Hope this solves your issue.
This answer too might help someone.
When you are using spring framework for your API development, you may accidently import a wrong library for RequestBody and RequestHeader annotations.
In my case, I accidently imported library,
io.swagger.v3.oas.annotations.parameters.RequestBody
This could arise the above issue.
Please ensure that, you are using the correct library which is
org.springframework.web.bind.annotation.RequestBody
I guess, it’s trying to call new List() which has no constructor. Try using ArrayList in your signatures.
If it works this way, you have found the error. Then rethink your concept of calling methods, since you would usually want to avoid using implementations of List in method signatures
Make sure your request type is not of type GET
If so it is better not to send data as request body.
you should write as below:
...
public String create(#RequestBody JSONObject requestParams) {
String name=requestParams.getString("name");
List<Planet> planetArtifacts=requestParams.getJSONArray("artifacts").toJavaList(Planet.Class);
...
I have a value object with an Optional<Integer> field. I want it to look like a string when it's serialized, but the Optional part is getting in the way.
I have the following class:
public class Person {
private com.google.common.base.Optional<Integer> age;
public com.google.common.base.Optional<Integer> getAge() {
return age;
}
public void setAge(Integer age) {
this.age = com.google.common.base.Optional.fromNullable(age);
}
}
Here is my test:
#Test
public void testPerson() throws Exception {
ObjectMapper objectMapper = new ObjectMapper().registerModule(new GuavaModule());
Person person = new Person();
person.setAge(1);
String jsonString = objectMapper.writeValueAsString(person);
assertEquals(jsonString, "{\"age\":\"1\"}");
}
It's failing with this output:
java.lang.AssertionError: expected [{"age":"1"}] but found [{"age":1}]
I have tried adding a #JsonSerialize annotation to the property, like this:
#JsonSerialize(using = ToStringSerializer.class)
private com.google.common.base.Optional<Integer> age;
But then the test fails like this:
java.lang.AssertionError: expected [{"age":"1"}] but found [{"age":"Optional.of(1)"}]
I've been looking around in com.fasterxml.jackson.datatype.guava.ser.GuavaOptionalSerializer but I can't seem to figure out how to use it correctly, if even if it's meant to be used this way.
What can I do to get this test working?
P.S. If it were up to me, I would remove the Optional altogether, but that's not an option.
I ended up creating my own simple serializer:
public class OptionalStringSerializer extends JsonSerializer<Optional<Object>> {
#Override
public void serialize(Optional<Object> value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
gen.writeString(value.isPresent() ? value.get().toString() : null);
}
}
This is how I specified it:
#JsonSerialize(using = OptionalStringSerializer.class)
private com.google.common.base.Optional<Integer> age;
class employee{
...
private long phone;
...
}
I want to validate phone number using spring jsr303 validator, In my Controller I am using #valid. I am successfully validating entered value is number or string by using generic typeMismatch placing in error message property file.
But I want to validate entered number format is correct or not.(#pattern for string only)
How to achieve this one,please suggest me.
Normally phone numbers are String and you can validate by using #Pattern, but if you want to validate any fields you can do like this.
Custom annotation Javax validator
#javax.validation.Constraint(validatedBy = { PhoneNumberConstraintValidator.class })
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
public #interface ValidPhoneNumber {
}
public class PhoneNumberConstraintValidator implements ConstraintValidator<ValidPhoneNumber, Long> {
#Override
public void initialize(final ValidPhoneNumber constraintAnnotation) {
// nop
}
#Override
public boolean isValid(final Long value, final ConstraintValidatorContext context) {
//your custom validation logic
}
}
class employee{
...
private long phone;
#ValidPhoneNumber
public Long getPhone() { return phone; }
...
}
OR simpler if you have hibernate validator, you can just add this method in your entity class.
#org.hibernate.validator.AssertTrue
public boolean validatePhoneNumber() { }
Following is how JSON string looks
{
"employee": {
"id": "c1654935-2602-4a0d-ad0f-ca1d514a8a5d",
"name": "smith"
...
}
}
Now i am using ObjectMapper#readValue(jsonAsStr,Employee.class) to convert it to JSON.
My Employee class is as follows...
#XmlRootElement(name="employee")
public class Employee implements Serializable {
private String id;
private String name;
...
public Employee() {
}
#XmlElement(name="id")
public String getId() {
return id;
}
public void setId(String id) {
this.id= id;
}
#XmlElement(name="name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
...
}
The exception I am getting is
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "employee" (class com.abc.Employee), not marked as
ignorable (12 known properties: , "id", "name", ... [truncated]])
I am not able to understand why "employee" is considered as a property. Am i wrong in assuming that only class members are considered as properties?
The problem is that a JSON Object { } maps to a Java class, and the properties in the JSON map to the Java properties. The first { } in your JSON (which you are trying to unmarshal to Employee), has a property employee, which Employee class does not have a property for. That's why you are getting the error. If you were to try and unmarshal only the enclosed { }
{
"id": "c1654935-2602-4a0d-ad0f-ca1d514a8a5d",
"name": "smith"
}
it would work as Employee has those properties. If you don't have control over the JSON, then you can configure the ObjectMapper to unwrap the root value
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
But the you might have another problem. The unwrapping is based on the annotation on the Employee class, either #JsonRootName("employee") or #XmlRootElement(name = "employee"). With the latter though, you need to make sure you have JAXB annotation support. For that, you need to have the jackson-module-jaxb-annotations, then register the module
mapper.registerModule(new JaxbAnnotationModule());
This applies for all your JAXB annotations you're using. Without this module, they won't work.
#peeskillet is right.
I was looking for a long time about how to use jax annotation to deserialize the json returned from server since I was getting UnrecognizedPropertyException as well.
Adding the following code fixed my problem:
mapper.registerModule(new JaxbAnnotationModule());
Follow below the entire code i used:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JaxbAnnotationModule());
List<PojoTO>response = mapper.readValue(result.readEntity(String.class), mapper.getTypeFactory().constructCollectionType(List.class, PojoTO.class));