JsonIncludeProperties with JsonUnwrapped - java

Can I use these annotation for my class to my expected json?
public class Staff {
private String name;
private Integer age;
#JsonUnwrapped
private Staff manager;
.... getter and setter ....
}
{
"name": "Fanny",
"age": 24,
"manager": "Timmy"
}
I know I can use JsonIgnoreProperties but I need to unwrap name only. Any solution? Thanks

You can have a getter for the name that returns a String and annotate it with #JsonPropery,
Can I use these annotation for my class to my expected json?
public class Staff {
private String name;
private Integer age;
#JsonIgnore
private Staff manager;
#JsonProperty("managerName")
public String getManagerName() {
return this.manager.getName();
}
.... getter and setter ....
}

Related

When does Jackson require no-arg constructor for deserialization?

In my spring boot project, I noticed a strange Jackson behavior. I searched over internet, found out what to do, but haven't found out why.
UserDto:
#Setter
#Getter
#AllArgsConstructor
public class UserDto {
private String username;
private String email;
private String password;
private String name;
private String surname;
private UserStatus status;
private byte[] avatar;
private ZonedDateTime created_at;
}
Adding a new user works just fine.
TagDto:
#Setter
#Getter
#AllArgsConstructor
public class TagDto {
private String tag;
}
Trying to add a new tag ends with an error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of TagDto (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
The solution to the problem was to add zero-arg constructor to the TagDto class.
Why does Jackson require no-arg constructor for deserialization in TagDto, while working just fine with UserDto?
Used same method for adding both.
My Tag and User entities are both annotated with
#Entity
#Setter
#Getter
#NoArgsConstructor
and have all args constructors:
#Entity
#Setter
#Getter
#NoArgsConstructor
public class User extends AbstractModel {
private String username;
private String password;
private String email;
private String name;
private String surname;
private UserStatus status;
#Lob
private byte[] avatar;
#Setter(AccessLevel.NONE)
private ZonedDateTime created_at;
public User(final String username, final String password, final String email, final String name, final String surname) {
this.username = username;
this.password = password;
this.email = email;
this.name = name;
this.surname = surname;
this.created_at = ZonedDateTime.now();
}
}
#Entity
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class Tag extends AbstractModel {
private String tag;
}
#MappedSuperclass
#Getter
public abstract class AbstractModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
}
Entity generation:
#PostMapping(path = "/add")
public ResponseEntity<String> add(#Valid #RequestBody final D dto) {
this.abstractModelService.add(dto);
return new ResponseEntity<>("Success", HttpStatus.CREATED);
}
public void add(final D dto) {
//CRUD repository save method
this.modelRepositoryInterface.save(this.getModelFromDto(dto));
}
#Override
protected Tag getModelFromDto(final TagDto tagDto) {
return new Tag(tagDto.getTag());
}
#Override
protected User getModelFromDto(final UserDto userDto) {
return new User(userDto.getUsername(), userDto.getPassword(), userDto.getEmail(), userDto.getName(), userDto.getSurname());
}
Error occurs when parsing JSON
{"tag":"example"}
sent via postman localhost:8081/tag/add, returns
{
"timestamp": "2020-09-26T18:50:39.974+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/tag/add"
}
I am using Lombok v1.18.12 and Spring boot 2.3.3.RELEASE with Jackson v2.11.2.
TL;DR: Solution is at the end.
Jackson supports multiple ways of creating POJOs. The following lists the most common ways, but it likely not a complete list:
Create instance using no-arg constructor, then call setter methods to assign property values.
public class Foo {
private int id;
public int getId() { return this.id; }
#JsonProperty
public void setId(int id) { this.id = id; }
}
Specifying #JsonProperty is optional, but can be used to fine-tune the mappings, together with annotations like #JsonIgnore, #JsonAnyGetter, ...
Create instance using constructor with arguments.
public class Foo {
private int id;
#JsonCreator
public Foo(#JsonProperty("id") int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Specifying #JsonCreator for the constructor is optional, but I believe it is required if there is more than one constructor. Specifying #JsonProperty for the parameters is optional, but is required for naming the properties if the parameter names are not included in the class file (-parameters compiler option).
The parameters imply that the properties are required. Optional properties can be set using setter methods.
Create instance using factory method.
public class Foo {
private int id;
#JsonCreator
public static Foo create(#JsonProperty("id") int id) {
return new Foo(id);
}
private Foo(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Create instance from text value using String constructor.
public class Foo {
private int id;
#JsonCreator
public Foo(String str) {
this.id = Integer.parseInt(id);
}
public int getId() {
return this.id;
}
#JsonValue
public String asJsonValue() {
return Integer.toString(this.id);
}
}
This is useful when a the POJO has a simply text representation, e.g. a LocalDate is a POJO with 3 properties (year, month, dayOfMonth), but is generally best serialized as a single string (yyyy-MM-dd format). #JsonValue identifies the method to be used during serialization, and #JsonCreator identifies the constructor/factory-method to be used during deserialization.
Note: This can also be used for single-value construction using JSON values other than String, but that is very rare.
Ok, that was the background information. What is happening for the examples in the question, it that UserDto works because there is only one constructor (so #JsonCreator is not needed), and many arguments (so #JsonProperty is not needed).
However, for TagDto there is only a single-argument constructor without any annotations, so Jackson classifies that constructor as a type #4 (from my list above), not a type #2.
Which means that it is expecting the POJO to be a value-class, where the JSON for the enclosing object would be { ..., "tag": "value", ... }, not { ..., "tag": {"tag": "example"}, ... }.
To resolve the issue, you need to tell Jackson that the constructor is a property initializing constructor (#2), not a value-type constructor (#4), by specifying #JsonProperty on the constructor argument.
This means that you cannot have Lombok create the constructor for you:
#Setter
#Getter
public class TagDto {
private String tag;
public TagDto(#JsonProperty("tag") String tag) {
this.tag = tag;
}
}

Json output to exclude field name, but include its class fields

Say I have 2 classes, one includes the other
class TestClass {
private int id;
private String name;
private AnotherClass another
}
class AnotherClass {
private String details
}
I would like json output for TestClass to only include AnotherClass's field directly and not show the field another:
{
"id": 1,
"name": "test",
"details": "test details"
}
I found the solution.Use #JsonUnwrapped on field another.
Thanks
If using Jackson, you can use the JsonUnwrapped annotation:
class TestClass {
private int id;
private String name;
#JsonUnwrapped
private AnotherClass another
}
class AnotherClass {
private String details
}

How to get Entity from RestController depending on mapping URL

I have MyEntity class:
#Entity
#Table("entities)
public class MyEntity {
#ID
private String name;
#Column(name="age")
private int age;
#Column(name="weight")
private int weight;
...getters and setters..
}
In #RestController there are 2 #GetMapping methods.
The first:
#GetMapping
public MyEntity get(){
...
return myEntity;
}
The second:
#GetMapping("url")
public List<MyEntity> getAll(){
...
return entities;
}
It's needed to provide:
1. #GetMapping returns entity as it's described in MyEntity class.
2. #GetMapping("url") returns entities like one of its fields is with #JsonIgnore.
UPDATE:
When I return myEntity, client will get, for example:
{
"name":"Alex",
"age":30,
"weight":70
}
I want in the same time using the same ENTITY have an opportunity depending on the URL send to client:
1.
{
"name":"Alex",
"age":30,
"weight":70
}
2.
{
"name":"Alex",
"age":30
}
You could also use JsonView Annotation which makes it a bit cleaner.
Define views
public class View {
static class Public { }
static class ExtendedPublic extends Public { }
static class Private extends ExtendedPublic { }
}
Entity
#Entity
#Table("entities)
public class MyEntity {
#ID
private String name;
#Column(name="age")
private int age;
#JsonView(View.Private.class)
#Column(name="weight")
private int weight;
...getters and setters..
}
And in your Rest Controller
#JsonView(View.Private.class)
#GetMapping
public MyEntity get(){
...
return myEntity;
}
#JsonView(View.Public.class)
#GetMapping("url")
public List<MyEntity> getAll(){
...
return entities;
}
Already explained here:
https://stackoverflow.com/a/49207551/3005093
You could create two DTO classes, convert your entity to the appropriate DTO class and return it.
public class MyEntity {
private String name;
private int age;
private int weight;
public PersonDetailedDTO toPersonDetailedDTO() {
PersonDetailedDTO person = PersonDetailedDTO();
//...
return person;
}
public PersonDTO toPersonDTO() {
PersonDTO person = PersonDTO();
//...
return person;
}
}
public class PersonDetailedDTO {
private String name;
private int age;
private int weight;
}
public class PersonDTO {
private String name;
private int age;
}
#GetMapping
public PersonDTO get() {
//...
return personService.getPerson().toPersonDTO();
}
#GetMapping("/my_url")
public PersonDetailedDTO get() {
//...
return personService.getPerson().toPersonDetailedDTO();
}
EDIT:
Instead of returning an Entity object, you could serialize it as a Map, where the map keys represent the attribute names. So you can add the values to your map based on the include parameter.
#ResponseBody
public Map<String, Object> getUser(#PathVariable("name") String name, String include) {
User user = service.loadUser(name);
// check the `include` parameter and create a map containing only the required attributes
Map<String, Object> userMap = service.convertUserToMap(user, include);
return userMap;
}
As an example, if you have a Map like this and want
All Details
userMap.put("name", user.getName());
userMap.put("age", user.getAge());
userMap.put("weight", user.getWeight());
Now if You do not want to display weight then you can put only two
parameters
userMap.put("name", user.getName());
userMap.put("age", user.getAge());
Useful Reference 1 2 3

java orika complex mapping

I use Orika and I want map codeActivite1.value from Value.class to tasks.tasks[0].notes of Ligne.class
mapperFactory.classMap(Value.class, Ligne.class).field().field("codeActivite1.value", "tasks.tasks[0].notes").register();
public class Value {
#SerializedName("code_activite1")
private CodeActivite1 codeActivite1;
//getter setter
}
public class CodeActivite1 {
#SerializedName("value")
private String value;
//getter setter
}
public class Ligne {
private Tasks tasks;
//getter setter
}
public class Tasks{
private Task[] tasks;
//getter setter
}
public class Task {
private String notes;
//getter setter
}
ma.glasnost.orika.MappingException: java.lang.IllegalArgumentException: java.lang.String is an unsupported source class for constructing instances of com.xxxx.business.xxxx.bean.Task[]
I solve the problem when I change array by List to Tasks.class
public class Tasks {
private List<Task> tasks;
//getter setter
}
mapperFactory.classMap(Value.class, Ligne.class).field().field("codeActivite1.value", "tasks.tasks[0].notes").register();

Jackson XML: nested field deserialization

I have the following xml
<MyPojo>
<name>Jason</name>
<age>25</age>
<meta>
<occupation>Engineer</occupation>
</meta>
</MyPojo>
I need to deserialize it to the following POJO:
public class MyPojo {
private String name;
private int age;
private String occupation;
}
The problem here is that occupation is wrapped within meta element
You need one more object:
public class MyPojo {
private String name;
private int age;
private Meta meta;
}
public class Meta{
private String occupation;
}
My idea is to replace occupation with an own class. Something like myMeta or whatever you want to call it(be aware in your case like the xml says: meta). This class should cotain the field occupation:
public class Meta
{
private String occupation;
}
After that you only have to add a new field of your new class e.g. myMeta to myPojo. Something like this:
public class MyPojo
{
private String name;
private int age;
private Meta meta;
}
this should avoid
that occupation is wrapped within meta element
Hope that helps!

Categories