I have these two Java classes
public class Artist {
#Id
private Integer id;
private String name;
private Short birthYear;
#JsonIgnore // This is Jackson
#OneToMany(mappedBy = "artist", fetch = FetchType.EAGER) // This is JPA
private List<Album> albums = new ArrayList<Album>();
. . .
#Override
public String toString() {
return name;
}
}
public class Album {
#Id
private Integer id;
private String name;
private Short releaseYear;
#ManyToOne // This is JPA
private Artist artist;
}
Now, what I want is when I produce JSON objects of the Album class it returns something like this:
{
id : 2012000587,
name : 'Escultura',
releaseYear : '2012',
artist : 'Guaco'
}
Right now it outputs:
{
id : 2012000587,
name : 'Escultura',
releaseYear : '2012',
artist : {
id : 2044452000,
name : 'Guaco',
birthYear : 1987
}
}
I want to avoid at any cost using custom serializers for this matter.
How can I do that?
Try to add a getter for name property and annotation it with a #JsonValue annotation.
public class Artist {
private String name;
...
#JsonValue
public String getName() { return name; }
}
Here is the link to the Jackson Wiki page for reference.
Related
First, I apologize if my english is unclear ; I am french.
I also am a very junior developer, and this is my first real personal project with no tutorial or whatsoever.
I am having some trouble with my Rest api.
I use java 11 and Spring/JPA
I have two DO classes that each represent a table in the database : Artist and Country.
An artist can have several nationalities, and a country can have several artist born in it.
So that means : many to many.
I joined them with an Association table ; ArtistNationality, that is also a class.
I know I could do without an additional class but, since in my app some relations also have some extra-fields (like the year of an award) I decided that all many to many relationships would be materialized the same way, by "join"classes (sorry I really have an hard time to explain in english)
When I create an Artist, I want my response json to contain the created artist with all its nationalities. But it always comes null.
The creation works fine. But here is the response :
What is odd is the results of my API call.
Here is the result of POST method :
{
"artistFirstName": "OH",
"artistLastName": "Test",
"artistBiography": "Je suis un test.",
"artistBirthDate": "1380-11-10",
"artistDeathDate": "1500-11-12",
"artistNationalities": [],
"artist_ID": 3
}
As you can see, nationalities come null, always.
What is expected, is the same as when I do a find or findall :
{
"artistFirstName": "OH",
"artistLastName": "Test",
"artistBiography": "Je suis un test.",
"artistBirthDate": "1380-11-10",
"artistDeathDate": "1500-11-12",
"artistNationalities": [
{
"nationality": {
"countryId": 1,
"countryName": "Andorre",
"countryShortCode": "AD",
"countryFlagFileName": "ad_16.png"
},
"nationalityId": 5
},
{
"nationality": {
"countryId": 12,
"countryName": "Autriche",
"countryShortCode": "AT",
"countryFlagFileName": "at_16.png"
},
"nationalityId": 6
}
],
"artist_ID": 3
}
What I don't understand is that my save method returns the result of a "find" method so why isn't it the same ?? Find and findall work perfectly, and the insertion also works fine.
Here are the DataObject classes, I shortened them to leave only the fields related to question but of course they all come with their constructors and getters/setter stuff :
Artist class :
#Entity
#Table(name = "artist")
public class Artist implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_artist")
private final Integer ARTIST_ID;
#OneToMany(targetEntity = ArtistNationality.class, mappedBy = "artistIdAsForeignKey", cascade = CascadeType.ALL)
#JsonManagedReference
private List<ArtistNationality> artistNationalities;
Artist DTO
public class ArtistDto {
private final Integer ARTIST_ID;
private String artistFirstName;
private String artistLastName;
private String artistBiography;
private String artistBirthDate;
private String artistDeathDate;
private List<NationalityDto> artistNationalities;
Country class :
#Entity
#Table(name = "country")
public class Country implements Serializable {
#Id
#Column(name = "id_country")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private final Integer COUNTRY_ID;
#OneToMany(targetEntity = ArtistNationality.class, mappedBy = "countryIdAsForeignKey", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonBackReference
private List<ArtistNationality> artistsComingFromCountry;
** COUNTRY DTO **
public class CountryDto {
private int countryId;
private String countryName;
private String countryShortCode;
private String countryFlagFileName;
ArtistNationality class :
#Entity
#Table(name="artist_x_nationality")
public class ArtistNationality implements Serializable {
#Id
#GeneratedValue
#Column(name="id_nationality")
private final Integer NATIONALITY_ID;
#ManyToOne (fetch = FetchType.LAZY)
#JsonBackReference
#JoinColumn(name= "fk_nationality_to_artist")
private Artist artistIdAsForeignKey;
#ManyToOne (fetch = FetchType.LAZY)
#JsonBackReference
#JoinColumn(name="fk_nationality_to_country")
private Country countryIdAsForeignKey;
Nationality Dto:
public class NationalityDto {
private final Integer NATIONALITY_ID;
private CountryDto nationality;
ArtistService :
#Service
public class ArtistServiceImpl implements IArtistService {
#Autowired
private IArtistDao artistDao;
#Autowired
private IArtistDoDtoMapper mapper;
#Autowired
private IArtistValidator validator;
#Autowired
private IArtistNationalityDao nationalityDao;
#Override
#Transactional(readOnly = true)
public List<ArtistDto> findAll() {
List<ArtistDto> resultList = new ArrayList<ArtistDto>();
List<Artist> artistsFromDatabase = artistDao.findAll();
if (artistsFromDatabase != null && !artistsFromDatabase.isEmpty()) {
resultList = mapper.mapDoListToDto(artistsFromDatabase);
}
return resultList;
}
#Override
#Transactional(readOnly = true)
public ArtistDto find(final int id) {
Optional<Artist> optArtistFromDatabase = artistDao.findById(id);
if (!optArtistFromDatabase.isPresent()) {
throw new ResourceNotFoundException();
}
Artist artistFromDatabase = optArtistFromDatabase.get();
ArtistDto result = mapper.mapDoToDto(artistFromDatabase);
return result;
}
#Override
#Transactional(rollbackFor = Exception.class)
public ArtistDto save(final ArtistDto objDto) {
if(validator.isValidForDatabase(objDto)){
//Save without nationalities (mapper does not map it);
Artist artistToSave = mapper.mapDtoToDo(objDto);
int artistId = artistDao.save(artistToSave).ARTIST_ID();
//Add the id of the artist to all his nationalities, then save the nationalities
for (NationalityDto nationality : objDto.getArtistNationalities()){
ArtistNationality doNationality = new ArtistNationality(nationality.getNationalityId());
doNationality.setArtistIdAsForeignKey(new Artist(artistId));
doNationality.setCountryIdAsForeignKey(new Country(nationality.getNationality().getCountryId()));
nationalityDao.save(doNationality);
}
ArtistDto returnArtist = this.find(artistId);
return returnArtist;
}
throw new InsertionException("Invalid object. Could not insert into database.");
}
Mappers :
#Override
#Transactional(rollbackFor = Exception.class)
public ArtistDto mapDoToDto(final Artist pDataObject) {
ArtistDto artistDto = new ArtistDto(pDataObject.ARTIST_ID());
if (artistValidator.isNotNullAndNotEmpty(pDataObject.getArtistFirstName())) {
artistDto.setArtistFirstName(pDataObject.getArtistFirstName());
}
if (artistValidator.isOptionPresent(pDataObject.getArtistLastName())) {
artistDto.setArtistLastName(pDataObject.getArtistLastName());
}
if (artistValidator.isOptionPresent(pDataObject.getArtistBiography())) {
artistDto.setArtistBiography(pDataObject.getArtistBiography());
}
if (artistValidator.isOptionPresent(pDataObject.getArtistBirthDate())) {
artistDto.setArtistBirthDate(pDataObject.getArtistBirthDate());
}
if (artistValidator.isOptionPresent(pDataObject.getArtistDeathDate())) {
artistDto.setArtistDeathDate(pDataObject.getArtistDeathDate());
}
List<NationalityDto> artistNationalities = new ArrayList<NationalityDto>();
if (artistValidator.isOptionPresent(pDataObject.getArtistNationalities())) {
System.out.println("yes, we're in !");
for (ArtistNationality nationality : pDataObject.getArtistNationalities()) {
NationalityDto nDto = new NationalityDto(nationality.getNationalityId());
CountryDto cDto = countryMapper.mapDoToDto(nationality.getCountryIdAsForeignKey());
nDto.setNationality(cDto);
artistNationalities.add(nDto);
}
}
artistDto.setArtistNationalities(artistNationalities);
return artistDto;
}
/**
* Note : we add nationality separately since we do not have Artist's ID yet.
*/
#Override
#Transactional(rollbackFor = Exception.class)
public Artist mapDtoToDo(final ArtistDto pDataTransfertObject) {
System.out.println(pDataTransfertObject.toString());
Artist artist = new Artist(pDataTransfertObject.getARTIST_ID());
if (artistValidator.isNotNullAndNotEmpty(pDataTransfertObject.getArtistFirstName())) {
artist.setArtistFirstName(pDataTransfertObject.getArtistFirstName());
}
if (artistValidator.isOptionPresent(pDataTransfertObject.getArtistLastName())) {
artist.setArtistLastName(pDataTransfertObject.getArtistLastName());
}
if (artistValidator.isOptionPresent(pDataTransfertObject.getArtistBiography())) {
artist.setArtistBiography(pDataTransfertObject.getArtistBiography());
}
if (artistValidator.isOptionPresent(pDataTransfertObject.getArtistBirthDate())) {
artist.setArtistBirthDate(pDataTransfertObject.getArtistBirthDate());
}
if (artistValidator.isOptionPresent(pDataTransfertObject.getArtistDeathDate())) {
artist.setArtistDeathDate(pDataTransfertObject.getArtistDeathDate());
}
return artist;
}
This is the Json I send to my controller :
{
"ARTIST_ID" : null,
"artistFirstName":"OH",
"artistLastName":"Test",
"artistBiography":"Je suis un test.",
"artistBirthDate":"1380-11-10",
"artistDeathDate": "1500-11-12",
"artistNationalities": [{
"nationality": {
"countryId" : 1
}
},
{"nationality":{
"countryId": 12
}
}
]
}
I also had to find a trick to save nationalities after artist because when I sent nationalities to database, jpa did not automatically add the saved artist to the nationality and the nationality was saved only with the country data, as you can see in the save method and the dto to do mapper.
I am sorry if it is not really clear, I do my best in english, thank you for understanding.
If your find and findAll is working then, you should be able to override return from save and use find jpa method using the artist id returned by save in your service layer that way you will have a complete json to return.
I have a Java enum where I store different statuses:
public enum BusinessCustomersStatus {
A("active", "Active"),
O("onboarding", "Onboarding"),
NV("not_verified", "Not Verified"),
V("verified", "Verified"),
S("suspended", "Suspended"),
I("inactive", "Inactive");
#Getter
private String shortName;
#JsonValue
#Getter
private String fullName;
BusinessCustomersStatus(String shortName, String fullName) {
this.shortName = shortName;
this.fullName = fullName;
}
// Use the fromStatus method as #JsonCreator
#JsonCreator
public static BusinessCustomersStatus fromStatus(String statusText) {
for (BusinessCustomersStatus status : values()) {
if (status.getShortName().equalsIgnoreCase(statusText)) {
return status;
}
}
throw new UnsupportedOperationException(String.format("Unknown status: '%s'", statusText));
}
}
Full code: https://github.com/rcbandit111/Search_specification_POC/blob/main/src/main/java/org/merchant/database/service/businesscustomers/BusinessCustomersStatus.java
The code works well when I want to get the list of items into pages for the value fullName because I use #JsonValue annotation.
I have a case where I need to get the shortValue for this code:
return businessCustomersService.findById(id).map( businessCustomers -> businessCustomersMapper.toFullDTO(businessCustomers));
Source: https://github.com/rcbandit111/Search_specification_POC/blob/316c97aa5dc34488771ee11fb0dcf6dc1e4303da/src/main/java/org/merchant/service/businesscustomers/BusinessCustomersRestServiceImpl.java#L77
But I get fullValue. Do you know for a single row how I can map the shortValue?
I'd recommend serializing it as an object. This can be done via the #JsonFormat annotation at the class level:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum BusinessCustomersStatus {
A("active", "Active"),
O("onboarding", "Onboarding"),
//...
#Getter
private String shortName;
#Getter
private String fullName;
//...
This will lead to the following result when serializing this enum for BusinessCustomersStatus.A:
{"shortName":"active","fullName":"Active"}
Alternatively, you could define status field as String:
public class BusinessCustomersFullDTO {
private long id;
private String name;
private String businessType;
private String status;
}
and map its value like this:
businessCustomersFullDTO.status(businessCustomers.getStatus().getShortName());
I am trying to learn Spring Framework on the go. During runtime I get following stacktrace:
Validation failed for object='title'. Error count: 1
org.springframework.validation.BindException:
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'title' on field 'status': rejected value
[Received];
I noticed that the problem is in the status, which is formatted by enum, but I can't any error.
My class Controller:
#Controller
#RequestMapping("/titles")
public class registerTitleController {
#RequestMapping("/title")
public String new() {
return "RegisterTitle";
}
#Autowired
private Titles titles;
#RequestMapping(method=RequestMethod.POST)
public String saveIn(Title title) {
titles.save(title);
return "RegisterTitle";
}
}
My class entity
#Entity
public class Title {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long cod;
private String description;
#DateTimeFormat(pattern="dd/MM/yyyy")
#Temporal(TemporalType.DATE)
private Date dateV;
private BigDecimal val;
#Enumerated(value = EnumType.STRING)
private StatusTitle status;
//other accessor methods
My class enum
public enum StatusTitle {
PENDING("Pending"),
RECEIVED("Received");
private String description;
private StatusTitulo(String descricao){
this.description = description;
}
public String getDescription() {
return description;
}
}
My system work without the status of the attribute.
Can someone point out what is wrong? Your help will be much appreciated.
You probably are sending "Received", but you need to send "RECEIVED" string to properly convert to the ENUM by default.
I am creating an application with embedded review documents inside Course documents with Spring Data Rest and MongoDB but I am unable to get reviews for a course. Here is my controller:
#Controller
#RequestMapping("/courses")
public class CourseController {
private final CourseRepository courseRepository;
public CourseController(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
#PatchMapping("/add-review")
public List<Review> addReview(#RequestBody AddReviewDto addReviewDto) {
Course course = courseRepository.findById(addReviewDto.getCourseId()).get();
Review review = new Review(new ObjectId().toString(), addReviewDto.getReview());
List<Review> reviews = course.getReviews();
reviews.add(review);
course.setReviews(reviews);
return courseRepository.save(course).getReviews();
}
#GetMapping("/{id}/reviews")
public List<Review> getAllReviewsForCourse(#PathVariable String id) {
Course course = courseRepository.findById(id).get();
return course.getReviews();
}
}
Here is the Course model:
#Getter
#Setter
#Document(collection = "courses")
#AllArgsConstructor
#NoArgsConstructor
public class Course {
public Course(#NotNull String code, #NotNull String name,
#NotNull String type, List<Review> reviews) {
this.code = code;
this.name = name;
this.type = type;
this.reviews = reviews;
}
#Id
private String id;
#NotNull
private String code;
#NotNull
private String name;
#NotNull
private String type;
private List<Review> reviews = new ArrayList<>();
}
And Review model:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Review {
#Id
private String id;
private String reviewText;
private String userName;
private String userId;
public Review(String id, Review other) {
this.id = id;
this.reviewText = other.reviewText;
this.userId = other.userId;
this.userName = other.userName;
}
}
When I send a request to http://localhost:8888/courses/605dc41f54beac4412cabadc, I successfully get reviews inside the course object as follows:
{
"code": "CS 101",
"name": "Introduction to Programming",
"type": "Lecture",
"reviews": [
{
"reviewText": "dfgsfgdgdg",
"userName": "yigit",
"userId": "604a9382777a83b08307c7e8"
}
]
}
But when I try to send the request to localhost:8888/courses/605dc41f54beac4412cabadc/reviews, I get 404 not found.
I debugged my code and seen that the code is running the correct controller, finding the course object and its reviews are visible in debugger but when I return course.getReviews(), it doesnt work.
You should be sending to http://localhost:8888/courses/605dc41f54beac4412cabadc/reviews
Try to open this URL in your browser
of course you get 404. Because your #RequestMapping is "/courses". try send the request to http://localhost:8888/courses/{id}/reviews
Turns out, I have used #Controller instead of #RestController...
I'm using Jackson mixins to only serialize out specific fields.
My ObjectMapper is configured like so:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.addMixIn(Person.class, SyncPerson.class);
mapper.addMixIn(TransactionLog.class, TransactionLogExport.class);
Here are the model classes paired with the JSON mixin objects that I'd like to export:
// Model class
public class Person {
private Long id;
private String email;
private String firstName;
private String lastName;
}
// Desired JSON format. Excludes 'id' field
public interface SyncPerson {
#JsonProperty("firstName")
String getFirstName();
#JsonProperty("lastName")
String getLastName();
#JsonProperty("email")
String getEmail();
}
// Model class
public class TransactionLog {
private long id;
private Integer version;
private Person person;
private Date date;
private EntityAction action;
}
// Desired JSON format. Excludes 'id' field, 'version', 'date'
public interface TransactionLogExport {
#JsonProperty("id")
String getId();
#JsonProperty("person")
Person person();
#JsonProperty("action")
EntityAction getAction();
}
Yet, my tests are showing that the person attribute of the TransactionLog isn't coming through.
#Test
public void testWriteValue() throws Exception {
Person person = new Person();
person.setEmail("a#c.com");
person.setFirstName("A");
person.setLastName("C");
TransactionLog log = new TransactionLog();
log.setId(0L);
log.setAction(EntityAction.CREATE);
log.setPerson(person);
log.setStartValue("start");
log.setEndValue("end");
log.setChanges("change");
String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(log);
System.out.println(prettyJson);
// Prints:
// {
// "id" : 0,
// "action" : "CREATE",
}
}
If I try the same test with a regular ObjectMapper mapper = new ObjectMapper(); instead of the mixin, then I see the full object exported, including the Person with email, names, etc. So something must be wrong with how I've configured the mixin... or else I'm misunderstanding something.
So can anyone help indicate what I could do to export out the subtype 'person' in my mixin?
Thanks!
Finally figured out the issue. The test now prints what we want:
{
“id” : 0,
“person” : {
“email” : “a#c.com”,
“firstName” : “A”,
“lastName” : “C”
},
“action” : “CREATE”
}
The mistake was in TransactionLogExport. It needs to say:
#JsonProperty("person")
Person getPerson();
Instead of:
#JsonProperty("person")
Person person();
I.e. the method needs to start with 'get'.