I am really confused how jackson (2.9.6 version) ObjectMapper works with #ConstructorProperties annotation.
It seems that mapper ignores property names which are present in a #ConstructorPropertiesannotation value method.
What's even more interesting - mapper works correctly regardless of properties names.
What I am talking about?
Let's consider custom XmlMapper:
private static final ObjectMapper XML_MAPPER = new XmlMapper()
.setAnnotationIntrospector(
AnnotationIntrospector.pair(
new JaxbAnnotationIntrospector(),
new JacksonAnnotationIntrospector()
)
)
.registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE);
and simple Data Transfer Object (DTO):
#XmlRootElement(name = "person")
#XmlAccessorType(XmlAccessType.NONE)
static class Person {
#XmlAttribute
final int age;
#XmlAttribute
final String name;
#XmlAttribute
final LocalDate dateOfBirth;
#ConstructorProperties({"age","name","dateOfBirth"})
public Person(int age, String name, LocalDate dateOfBirth) {
this.age = age;
this.name = name;
this.dateOfBirth = dateOfBirth;
}
#Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", dateOfBirth=" + dateOfBirth +
'}';
}
}
I created test to reproduce the issue:
#Test
#DisplayName("Check xml deseralization for Person class")
void deserialization() throws IOException {
String xml = "<person age=\"26\" name=\"Fred\" date-of-birth=\"1991-11-07\"/>";
Person person = XML_MAPPER.readValue(xml, Person.class);
Assertions.assertEquals("Person{age=26, name='Fred', dateOfBirth=1991-11-07}", person.toString());
}
It's strange for me why test is passed regardless of #ConstructorProperties annotation. The test passed with annotation
#ConstructorProperties({"a","b","c"})
public Person(int age, String name, LocalDate dateOfBirth) {
this.age = age;
this.name = name;
this.dateOfBirth = dateOfBirth;
}
Is it a magic? How jackson processes this annotation? What is an equivalent in jackson annotations to ConstructorProperties?
It's passing because the JaxbAnnotationIntrospector can determine the property names from the #XmlAttribute annotations.
The doc on AnnotationIntrospectorPair says:
Helper class that allows using 2 introspectors such that one
introspector acts as the primary one to use; and second one
as a fallback used if the primary does not provide conclusive
or useful result for a method.
The JacksonAnnotationIntrospector (which understands the #ConstructorProperties annotation) isn't being used at all.
If you remove all the JAXB annotations your test will only pass when the correct names are specified in #ConstructorProperties.
If you want to do it "the jackson way", then remove the JAXB annotations and the JaxbAnnotationIntrospector completely (just drop the call to setAnnotationIntrospector, the mapper will default to using the JacksonAnnotationIntrospector).
Deserialisation will work, but you'll have to add some jackson native annotations if you want to achieve the same serialised form:
#JacksonXmlRootElement(localName = "person")
static class Person {
#JacksonXmlProperty(isAttribute = true)
final int age;
#JacksonXmlProperty(isAttribute = true)
final String name;
#JacksonXmlProperty(isAttribute = true)
final LocalDate dateOfBirth;
#ConstructorProperties({"age", "name", "dateOfBirth"})
public Person(int age, String name, LocalDate dateOfBirth) {
this.age = age;
this.name = name;
this.dateOfBirth = dateOfBirth;
}
//...
Related
I have a class like this in one of the microservices (I cannot change it):
public class MyClass {
String name;
Integer age;
String gender;
List<SomeObject> some objects;
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("age")
public String getAge() {
return name;
}
#JsonProperty("gender")
public String getGender() {
return name;
}
#JsonProperty("someObjects")
public List<SomeObject> getSomeObjects() {
return someObjects;
}
}
My task is to create JSON file basing on data from that class, but this file should only contain
'age' and 'name' property, and from SomeObject I should also extract part of the data to create a list.
I don't know how can I 'turn off' the properties, that I don't needed. Is it possible?
Yes, you can using Jackson ObjectMapper.
Add the dependency, with Maven, inside your pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
Create your custom entity without the fields you don't need on your client side :
public class CustomPerson {
String name;
Integer age;
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("age")
public String getAge() {
return name;
}
}
Use ObjectMapper to save any object or list of objects of CustomPerson POJO :
ObjectMapper objectMapper = new ObjectMapper();
// one person object saved in person.json file :
objectMapper.writeValue(new File("path/to/person.json"), person);
// many person objecs saved in persons.json file :
objectMapper.writeValue(Paths.get("persons.json").toFile(), persons);
The rest is trivial, you'll get it easily.
I am new spring boot developer and i am trying to develope and rest api . when I do it ,I get and issues that my api return two duplicated response in postman .But i haven't code anythiong to get duplicated valuese in my code . the one of duplicate values is my model clase variable and athor one is table's attribute name .
below response in postman
model class
public class person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY )
private Long id;
#Column(name = "name")
private String Name ;
#Column(name ="surname")
private String Surname;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public String getSurname() {
return Surname;
}
public void setSurname(String surname) {
Surname = surname;
}
}
repository
#Repository
public interface personRepository extends JpaRepository<person,Long> {
}
controller
#RestController
#RequestMapping("/person")
public class personController {
#Autowired
private personRepository repository;
public personController(personRepository repository) {
this.repository = repository;
}
#GetMapping("/view/list/person")
private List<person> viewperson() {
return repository.findAll();
}
#PostMapping("/insert/person")
private person savePerson(#RequestBody person obj) {
return repository.save(obj);
}
#DeleteMapping("/delete/{id}")
private void delete(#PathVariable Long id) {
repository.deleteById(id);
}
}
application.properties
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialec
t
The problem is that you're not following the proper conventions in your naming strategy.
Due to this, Jackson doesn't know that your getters (getSurname(), getName()) are referencing the fields Surname and Name. That's why it serializes both your fields and your getters separately to JSON.
To fix this, you can follow the Java naming conventions and use a lowercase letter for the first character of your fields.
For example:
#Column(name = "name")
private String name; // Change this
#Column(name ="surname")
private String surname; // Change this
This will change your JSON output to:
{
"id": 1,
"name": "bryan",
"surname": "Nicky"
}
If you want to keep your JSON with capital letters, you can use the #JsonProperty annotation:
#JsonProperty("Name") // Add this
#Column(name = "name")
private String name;
#JsonProperty("Surname") // Add this
#Column(name ="surname")
private String surname;
Unrelated to your question, but according to those naming conventions, your classes should start with a capital (eg. Person, PersonController, PersonRepository, ...).
It is possible to deserialize to a class with private fields and a custom argument constructor without using annotations and without modifying the class, using Jackson?
I know it's possible in Jackson when using this combination: 1) Java 8, 2) compile with "-parameters" option, and 3) the parameters names match JSON. But it's also possible in GSON by default without all these restrictions.
For example:
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public static void main(String[] args) throws IOException {
String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";
System.out.println("GSON: " + deserializeGson(json)); // works fine
System.out.println("Jackson: " + deserializeJackson(json)); // error
}
public static Person deserializeJackson(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
return mapper.readValue(json, Person.class);
}
public static Person deserializeGson(String json) {
Gson gson = new GsonBuilder().create();
return gson.fromJson(json, Person.class);
}
}
Which works fine for GSON, but Jackson throws:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `jacksonParametersTest.Person` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{firstName: "Foo", lastName: "Bar", age: 30}"; line: 1, column: 2]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
It's possible in GSON, so I would expect that there must be some way in Jackson without modifying the Person class, without Java 8, and without an explicit custom deserializer. Does anybody know a solution?
Update, additional info
Gson seems to skip the argument constructor, so it must be creating a no-argument constructor behind the scenes using reflections.
Also, there exists a Kotlin Jackson module which is able to do this for Kotlin data classes, even without the "-parameters" compiler flag.
So it is strange that such a solution doesn't seem to exist for Java Jackson.
This is the (nice and clean) solution available in Kotlin Jackson (which IMO should also become available in Java Jackson via a custom module):
val mapper = ObjectMapper()
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.registerModule(KotlinModule())
val person: Person = mapper.readValue(json, Person::class.java)
Solution with mix-in annotations
You could use mix-in annotations. It's a great alternative when modifying the classes is not an option. You can think of it as kind of aspect-oriented way of adding more annotations during runtime, to augment the statically defined ones.
Assuming that your Person class is defined as follows:
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// Getters omitted
}
First define a mix-in annotation abstract class:
public abstract class PersonMixIn {
PersonMixIn(#JsonProperty("firstName") String firstName,
#JsonProperty("lastName") String lastName,
#JsonProperty("age") int age) {
}
}
Then configure ObjectMapper to use the defined class as a mix-in for your POJO:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
mapper.addMixIn(Person.class, PersonMixIn.class);
And deserialize the JSON:
String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";
Person person = mapper.readValue(json, Person.class);
Since there is no default constructor, jackson or gson want create instance by there own. you should tell to the API how to create such instance by providing
custom deserialize.
here an snippet code
public class PersonDeserializer extends StdDeserializer<Person> {
public PersonDeserializer() {
super(Person.class);
}
#Override
public Person deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
try {
final JsonNode node = jp.getCodec().readTree(jp);
final ObjectMapper mapper = new ObjectMapper();
final Person person = (Person) mapper.readValue(node.toString(),
Person.class);
return person;
} catch (final Exception e) {
throw new IOException(e);
}
}
}
Then register simple module as to handle your type
final ObjectMapper mapper = jacksonBuilder().build();
SimpleModule module = new SimpleModule();
module.addDeserializer(Person.class, new PersonDeserializer());
Jackson provides the module jackson-modules-java8 for solve your problem.
You must built your ObjectMapper as following:
ObjectMapper mapper = new ObjectMapper()
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
You must add -parameters as compiler argument.
Example for maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<!--somecode-->
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
For gradle:
compileJava {
options.compilerArgs << "-parameters"
}
Only if you can change your class implementation, below solution works
A simple solution is just to create a default constructor
Person() in Person class
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person() {
}
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
...
}
I need to hide some of the fields in the model class in my response object.
I tried to follow this SO answer
but with no luck.
when there are getter and setters for a field then the #JsonIgnore annotation doesn't seem to be working. see the following code snippet for clarifications.
#ApiModel(description = "")
public class APIInfoDTO {
private String id = null;
#JsonIgnore //this field will not be hidden when getters and setters are defined..
private String name = null;
private String status = null;
#JsonIgnore // this "info" field is hidden since there are no getters and setters for this field
private String info = "adncusdvshbdvsbvhdb";
/**
**/
#ApiModelProperty(value = "")
#JsonProperty("id")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
/**
**/
#ApiModelProperty(value = "")
#JsonProperty("name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
**/
#ApiModelProperty(value = "")
#JsonIgnore
public String getDescription() {
return description;
}
#JsonProperty("description")
public void setDescription(String description) {
this.description = description;
}
furthermore this is the code snippet for object mapping
public static APIInfoDTO fromAPIToInfoDTO(API api) {
APIInfoDTO apiInfoDTO = new APIInfoDTO();
apiInfoDTO.setDescription(api.getDescription());
apiInfoDTO.setContext(api.getContext());
apiInfoDTO.setId(api.getUUID());
APIIdentifier apiId = api.getId();
apiInfoDTO.setName(apiId.getApiName());
apiInfoDTO.setVersion(apiId.getVersion());
apiInfoDTO.setProvider(apiId.getProviderName());
apiInfoDTO.setStatus(api.getStatus().toString());
String providerName = api.getId().getProviderName();
apiInfoDTO.setProvider(APIUtil.replaceEmailDomainBack(providerName));
return apiInfoDTO;
}
any helpful answer would be highly appreciated.. Thanks
[UPDATE] The #JsonIgnore works with org.codehaus.jackson:jackson-core-asl:1.8.6 but fails with com.fasterxml.jackson.core:jackson-annotations:2.7.2.. Any idea why???
Add #JsonIgnore Annotation to the getter method as well.
Or Try adding #JsonIgnoreProperties(value={"name"}) at Class level, if this is an option for you
UPDATE
If you have Proper Jackson Library in your classpath (group: 'com.fasterxml.jackson.core', name: 'jackson-core'), #JsonIgnore on your field will work just fine; as long as the getter method you have is a standard getter, you don't have to annotate getter with #JsonIgnore.
If you want to serialize and deserialize your object based only on fields annotations, the Jackson ObjectMapper should be configured to ignore getters and setters method:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE));
or
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
It can also be configured at Class level using the #JsonAutoDetect annotation.
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class APIInfoDTO {
// ...
}
My Bean class is as below. When the mapping happens, the JSON object contains duplicate values.
Response:
{"Id":"00PJ0000003mOgMMAU","Name":"web.xml","name":"web.xml","id":"00PJ0000003mOgMMAU"}
Why the values are getting duplicated?
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class AttachmentsMapper
{
#JsonProperty(value = "Id")
private String Id;
#JsonProperty(value = "Name")
private String Name;
public String getId() {
return Id;
}
public void setId(String Id) {
this.Id = Id;
}
public String getName() {
return Name;
}
public void setName(String Name) {
this.Name = Name;
}
}
It doesn't print duplicate the same field twice it prints 2 different fields that it finds. Jackson sees you want to print "name" because you have a getter called getName() and "Name" because you have annotated the Name field as #JsonProperty with a different key. It sees different fields because "name" != "Name". Two solutions :
Move the annotation to the getter. The field is ignored by default because it's private E.g.
#JsonProperty(value = "Name")
public String getName() {
return Name;
}
Use a more recent version of Jackson as you seem to be using 1.8 from com.codehaus. Use 1.9 from there or even better use the latest from com.fasterxml. I tried your code as it is with 1.9 and it worked without moving the annotation.
In Jackson 2 try to disable Jackson visibility for all the sources (getters, setters, fields, etc.) and then just enable the visibility for the object fields:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
...
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);