Jackson: Object Mapper annotations to deserialize a inner collection - java

I want to convert the following json into a java object, using as much as possible annotations.
{"user":{
"id":1,
"diets":[
{"diet":{
"name":"...",
"meals":[]
}
}
]
}
}
I'm getting trouble with the collection diets. I tried to use #JsonProperty but it doesn't work properly. Is there a special annotation for map inner aggregates?
Diet.java
#JsonRootName(value = "diet")
public class Diet {
#JsonProperty(value="name")
private String name;
#JsonProperty(value="meals")
private List<Meal> meals;
private User user;
// Rest of the class omitted.
}
User.java
#JsonRootName(value = "user")
public class User {
#JsonProperty("id")
private long id;
#JsonProperty("diets")
private List<Diet> diets = new ArrayList<Diet>();
// Rest of the class omitted.
}
Thanks!

The diets object in your json is not a List. Its a List of key-value pair with key "diet" and value a diet object. So you have three options here.
One is to create a wrapper object say DietWrapper and use List of diet wrapper in User like
#JsonRootName(value = "user")
class User {
#JsonProperty(value = "id")
private long id;
#JsonProperty(value = "diets")
private List<DietWrapper> diets;
//Getter & Setters
}
class DietWrapper {
#JsonProperty(value = "diet")
Diet diet;
}
Second option is to keep diest as simple list of maps like List>
#JsonRootName(value = "user")
class User {
#JsonProperty(value = "id")
private long id;
#JsonProperty(value = "diets")
private List<Map<String, Diet>> diets;
//Getter & Setters
}
Third option is to use a custom deserializer which would ignore your diet class.
#JsonRootName(value = "user")
class User {
#JsonProperty(value = "id")
private long id;
#JsonProperty(value = "diets")
#JsonDeserialize(using = DietDeserializer.class)
private List<Diet> diets;
//Getter & Setters
}
class DietDeserializer extends JsonDeserializer<List<Diet>> {
#Override
public List<Diet> deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(jsonParser);
List<Diet> diets = mapper.convertValue(node.findValues("diet"), new TypeReference<List<Diet>>() {});
return diets;
}
}

Related

Post request transform int field to string automatically

I'm doing a dummy app of a hostpital. The problem I'm having is that, I'm trying to verify that when a Patient is created, the fields passed are of the correct type, but whenever I POST an Int in a String field, it doesn't fail and just transform the Int to String. The field I'm trying to make fail is "surname", which by the definition of the Patient class, is a String.
If I do this (I pass a number to the "surname" field):
{
"name": "John",
"surname": 43,
"sickness": "headache"
}
It just transforms 43 into a String by the time its in the Controller method.
Here we have the Patient object:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
public class Patient implements Serializable {
private static final long serialVersionUID = 4518011202924886996L;
#Id
//TODO: posible cambiar luego la generationType
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "patient_id")
private Long id;
#Column(name = "patient_name")
#JsonProperty(required = true)
private String name;
#Column(name = "patient_surname")
#JsonProperty(required = true)
private String surname;
#Column(name = "patient_sickness")
#JsonProperty(required = true)
private String sickness;
}
And this is the controller class:
#Controller
#Path("/patient")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public class PatientController {
#POST
#Path("")
public ResponseEntity<Object> postPatient(final Patient patient) {
ResponseEntity<Object> createdPatient = patientBusiness.createPatient(patient);
return new ResponseEntity<Patient>(createdPatient.getBody(), createdPatient.getStatusCode());
}
EDIT 1:
Following the "clues" and closing the circle of attention, I tried modifying the ObjectMapper, but my configuration isn't applying. I'm still getting the error from above.
This is the config class:
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper getModifiedObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false);
mapper.coercionConfigFor(LogicalType.Integer).setCoercion(CoercionInputShape.String, CoercionAction.Fail);
return mapper;
}
}
Even added the property to the application.yml, but still nothing:
spring:
jackson:
mapper:
allow-coercion-of-scalars: false
Any help is appreciated. Thx.
In the end I referred to this post to do a deserializer and a module to just have it along all the program, not just the field I want not to be transformed.
Disable conversion of scalars to strings when deserializing with Jackson

Json Deserialize in multiple Java objects

I try to deserialize json :
{
"date": "2021_05",
"uuid": "3ba8b966-993f-49e0-b349-e528843a382c",
"dataset": "dataset",
"hmm_hit": "hit",
"hmm_evalue": "6.7e-186",
"hmm_score": "610.9"
},
I have two entities :
#Entity
public class HmmResult {
#Id
#GeneratedValue
#JsonIgnore
private Integer id;
#JsonProperty("hmm_hit")
private String hmm;
#JsonProperty("hmm_evalue")
private String eValue;
#JsonProperty("hmm_score")
private Float score;
}
and
#Entity
public class Protein {
#Id
#GeneratedValue
#JsonIgnore
private Integer id;
#JsonProperty("date")
private String date;
#JsonProperty("uuid")
private String uuid;
#JsonProperty("dataset")
private String dataset;
#OneToOne
#JsonDeserialize(as = HmmResult.class)
private HmmResult hmmResult;
How to deserialize both entities at the same time with one entry of json ?
Here is extract of main with Jackson ObjectMapper :
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Protein p = objectMapper.readValue(new File(file), Protein.class);
It parses "date", "uuid" and "dataset" fine but can not parse HmmResult object with "hmm_subfamily", "hmm_evalue" and "hmm_score" values : I get error : p.getHmmResult() : null. (HmmResult hm = objectMapper.readValue(new File(file), HmmResult.class); works fine too alone).
There is #JsonUnwrapped annotation for this. Should work like this:
public class Protein {
#JsonProperty("date")
private String date;
#JsonProperty("uuid")
private String uuid;
#JsonProperty("dataset")
private String dataset;
#JsonUnwrapped
private HmmResult hmmResult;
}
var protein = new ObjectMapper().readValue(new File(file), Protein.class);

querydsl how to return dto list?

i use querydsl, hibernate
i want select data by Dto in Dto list but not working
here is my code
#Data
#Entity
public class Team {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
#Entity
#Setter
public class Member {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "team_id")
private Team team;
}
#Setter
public class TeamDto {
private Long id;
private String name;
private List<MemberDto> members = new ArrayList<>();
}
#Setter
public class MemberDto {
private Long id;
private String name;
}
test
#BeforeEach
void setup() {
queryFactory = new JPAQueryFactory(em);
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member("memberA");
member.setTeam(team);
em.persist(member);
Member member2 = new Member("memberB");
member2.setTeam(team);
em.persist(member2);
em.flush();
em.clear();
}
#Test
void t1() {
TeamDto teamDto = queryFactory
.select(Projections.fields(
TeamDto.class,
team.id,
team.name,
Projections.fields(
MemberDto.class,
member.id,
member.name
).as("members")
))
.from(team)
.fetchOne();
System.out.println("teamDto = " + teamDto);
}
error log is = java.lang.IllegalArgumentException: com.blog.querydsltest.domain.dto.MemberDto is not compatible with java.util.List
what is problem?? is impossible bring data by List dto??
i try to change Projections.fields to bean, construct, ... but not working
how can i do ?
Multi level aggregations are currently not supported by QueryDSL. There are also no concrete plans to support it as of now.
For a DTO solution that can fetch associations with it, I recommend you to have a look at Blaze-Persistence Entity Views. With Entity Views the code for your DTO would look something like the following:
#EntityView(Team.class)
public interface TeamDto {
#IdMapping public Long getId();
#Mapping("name") public String getName();
#Mapping("members") public List<MemberDTO> getMembers();
}
If members is not an association on your TeamEntity, you can map it through a #MappingCorrelated binding.
Disclaimer: I am a contributor for Hibernate, QueryDSL and Blaze-Persistence.

How can I exclude properties when serializing an object to JSON with jackson?

I have a class implementing Serializable, which is mapped to a database table. It looks like this:
#Entity
#Table(name = "users")
public class Users implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
#Column(name = "name", nullable = false)
public String name;
#Column(name = "email", nullable = false)
public String email;
#Column(name = "status", nullable = false)
public String status;
}
For the most part, I want all these properties to be included in the JSON. However, there is a specific case where I want to exclude status, but I can't figure out a good way of doing this with Jackson.
My controller looks something like this:
public class UserController {
private final ObjectMapper mapper;
#Inject
public UserController(ObjectMapper mapper) {
this.mapper = mapper;
}
public CompletionStage<JsonNode> getUserList() {
// Get list of Users and return the JSON; with all User properties included
}
public CompletionStage<JsonNode> getUser(Long userId) {
// Get a single user from JPA, in a promise
return userDatabase.get(userId).thenApply(user -> { // user is type User
// Here, I don't want to include "status" in the JSON.
return mapper.valueToTree(user);
});
}
}
So when I do mapper.valueToTree(user), of course, it includes all properties of User, but I want to exclude status in this specific route/function while keeping it included in all other places its serialized.
I know I can use #JsonIgnore to ignore it always, but can I do this just sometimes?
Some solutions I thought of are:
filter through the properties and get rid of status
Copy user over to an ObjectNode and manually remove status
Neither of these seems ideal though, I feel like there has to be a cleaner approach with Jackson.

Jackson JSON serializer returns extra collection name

I have an list of things that I return as a jackson list. What I would like is this:
"things": { [
{
"id": "1234",
..list of students
but currently, I am getting this:
"things": {
"HashSet": [
{
"id": "1234",
I am using a JsonSerializer>, which is why it adds the HashSet field. I tried adding a json property on the field, but its not allowed since it is a local variable.
I am currently using the jackson library and have looked into:
Jackson annotation - How to rename element names?
How to rename root key in JSON serialization with Jackson
but they seem to have a different issue altogether. Any ideas? Thanks!
Edit:
Added the class implementations. Note that I call owner, that contains Things. Also, my jpa annotations are there as well. Thanks.
#Entity #Table(name = "owner")
public class Owner extends BaseEntity implements Persistence {
#Column
private String name;
#Column
private String description;
#Column
private Integer capacity;
#Column
#JsonSerialize(using = ThingSerializer.class)
#JsonProperty("things")
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "owners")
private Set<Thing> tihngs = new HashSet<>();
public class ThingSerializer extends JsonSerializer<Set<Thing>> {
#Override public void serialize(Set<Thing> thingSet, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws
IOException,
JsonProcessingException {
Set<Thing> things = new HashSet<>();
thingSet.forEach(thing -> {
Thing t = new Thing();
t.setCreatedBy(thing.getCreatedBy());
t.setCreationDate(thing.getCreationDate());
t.setId(thing.getId());
t.setDateModified(thing.getDateModified());
t.setModifiedBy(thing.getModifiedBy());
t.setStatus(thing.getStatus());
things.add(s);
});
jsonGenerator.writeObject(things);
}
}
Thing Entity
#Entity
#Table(name = "thing")
public class Thing extends BaseEntity implements Persistence {
private static final long serialVersionUID = 721739537425505668L;
private String createdBy;
private Date creationDate;
.
.
.
#OneToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "ThingOwner", joinColumns = #JoinColumn(name = "thing_id") , inverseJoinColumns = #JoinColumn(name = "owner_id") )
private Set<Owner> owners = new HashSet<>();
Why don't you use ObjectMapper to serialize and deserialize your data?
Have a look at this small test, i believe it does what you want:
#Test
public void myTest() throws Exception{
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(
mapper.getSerializationConfig().
getDefaultVisibilityChecker().
withFieldVisibility(JsonAutoDetect.Visibility.ANY).
withGetterVisibility(JsonAutoDetect.Visibility.NONE).
withSetterVisibility(JsonAutoDetect.Visibility.NONE).
withCreatorVisibility(JsonAutoDetect.Visibility.NONE).
withIsGetterVisibility(JsonAutoDetect.Visibility.NONE));
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
TestObject value = new TestObject();
value.a = "TestAvalue";
value.set = new HashSet<>();
value.set.add(new SetValueObject(1, 1));
value.set.add(new SetValueObject(2, 2));
String test = mapper.writeValueAsString(value);
System.out.println(test);
}
public class TestObject{
String a;
Set<SetValueObject> set;
}
public class SetValueObject{
public SetValueObject(int a, int b){
this.a = a;
this.b = b;
}
int a;
int b;
}
Output:
{"a":TestAvalue","set":[{"a":1,"b":1},{"a":2,"b":2}]}
Tested with Jackson 2.6.1
And i have modified one of my tests, so i'am not sure that you need all of this ObjectMapper config => it's just to give you an idea of another approach by using ObjectMapper.

Categories