One to Many save (POST) results in Bad Request (400) using Fetch for List<Entity> - spring-boot

I am trying to save Parent (One) and Children (Many) entities at the same time.
I took help from here and here.
I have an User Entity like below:
#Entity
#Table(name = "app_user")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class AppUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "salutation")
private String salutation;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#Column(name = "preference")
private String preference;
public AppUser(String salutation, String name, String email, String preference, List<Address> addressList,
List<Expertise> expertise) {
super();
this.salutation = salutation;
this.name = name;
this.email = email;
this.preference = preference;
this.addressList = addressList;
this.expertise = expertise;
}
#OneToMany(orphanRemoval = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "address_id")
private List<Address> addressList = new ArrayList<>();
#OneToMany(orphanRemoval = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "expertise_id")
private List<Expertise> expertise = new ArrayList<>();
My POST controller method.
#PostMapping("/appUsers")
public ResponseEntity<AppUser> createUser(#RequestBody AppUser appUser) {
try {
AppUser _appUser = appUserRepository.save(
new AppUser(appUser.getSalutation(), appUser.getName(), appUser.getEmail(),
appUser.getPreference(), appUser.getAddressList(),
appUser.getExpertise()));
return new ResponseEntity<>(_appUser, HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
My pure JS (Fetch) snippet:
<script>
async function postDataToServer(postData) {
const baseURL = "http://localhost:8080/api";
try {
const res = await fetch(`${baseURL}/appUsers`, {
method: "post",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(postData),
});
if (!res.ok) {
const message = `An error has occured: ${res.status} - ${res.statusText}`;
throw new Error(message);
}
} catch (err) {
alert(err.message);
}
}
</script>
Using above, I can see the form data nicely forming up like below:
{
"salutation": "Mr.",
"name": "Ajay Kumar",
"email": "ajay#kumar.com",
"address_main": "1234 StreetName State 12345",
"address_1": "2345 StreetName State 23456",
"address_2": "3456 StreetName State 34567",
"preference": "Vegeterian",
"expertise": [
"java",
"springboot",
"javascript"
],
"secret": "1abc1234-1abc-4321-1234-1234abcd1234"
}
During submit if I don't select expertise, it all works find. i.e. the user gets saved but if I select expertise checkboxes I get a 400 bad request message at the browser console and JSON parse erroSTS console like this:
2022-02-25 11:02:53.009 WARN 25007 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of com.spring.boot.rocks.model.Expertise (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('java'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.spring.boot.rocks.model.Expertise (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('java') at [Source: (PushbackInputStream); line: 1, column: 234] (through reference chain: com.spring.boot.rocks.model.AppUser["expertise"]->java.util.ArrayList[0])]
I created a github project here if any more details are required.
Question: What I am missing? How do I convert expertise collection to List using pure JS only ? Or how do I handle expertise collection in controller?

Your form data is not in correct format. This should be like this:
{
"salutation": "Mr.",
"name": "Ajay Kumar",
"email": "ajay#kumar.com",
"address_main": "1234 StreetName State 12345",
"address_1": "2345 StreetName State 23456",
"address_2": "3456 StreetName State 34567",
"preference": "Vegeterian",
"expertise": [
{
"java",
"springboot",
"javascript"
}
],
"secret": "1abc1234-1abc-4321-1234-1234abcd1234"
}
Expertise and address in your parent class are lists, not normal objectType entity. If any of these two lists are not present, try to set them as emptyList before saving.

Related

how to get response in json format

I am trying to fetch videos from student id and subject id . here i want to fetch only 3 columns from database with json format
Below is my code can anyone help me solve my issue.
Entity
public class Video extends AbstractEntity {
#Column(name = "video_name")
private String videoName;
private boolean active;
#Column(name = "media_location")
private String mediaLocation;
#Column(name = "media_type")
private String mediaType;
#Column(name = "class_id")
private Integer classId;
#Column(name = "subject_id")
private Integer subjectId;
}
Controller
#GetMapping("/video")
public ResponseEntity<?> getByClassidAndSubjectId(#RequestParam("class id") Integer classId,
#RequestParam("subject id") Integer subjectId) {
return service.getByClassidAndSubjectId(classId,subjectId);
}
Service
public ResponseEntity<?> getByClassidAndSubjectId(Integer classId, Integer subjectId) {
List<Video> videos = repository.getByClassIdAndSubjectId(classId, subjectId);
if (videos.isEmpty()) {
return new ResponseEntity<Object>("no videos found by classId " + classId + " AND subjectId " + subjectId,
HttpStatus.NOT_FOUND);
} else {
List<Object> video = repository.findByClassIdAndSubjectId(classId,subjectId);
return new ResponseEntity<Object>(video, HttpStatus.OK);
}
}
Repository
#Query(value = "SELECT id,video_name,media_location FROM video WHERE class_id=:classId AND subject_id=:subjectId", nativeQuery = true)
List<Object> findByClassIdAndSubjectId(#Param("classId")Integer classId,#Param("subjectId") Integer subjectId);
i am getting response like this
[
[
5,
"kannada",
"aws"
],
[
7,
"english",
"aws"
]
]
but i want response like
[
{
"id": 1,
"videoName": "xyz",
"videopath": "xyz"
},
{
"id": 2,
"videoName": "abc",
"videopath": "abc"
}
]
add json dependency your POM file https://mvnrepository.com/artifact/org.json/json
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
</dependency>
more details :- https://www.json.org/

Parsing nested json received from an api to objects in spring boot

I am creating a spring boot application which receives some JSON data from a 3rd party api. The JSON data has so many nested objects.I want to map them as Java objects. Below is the code I wrote for getting the api response.
public ResponseEntity<MovieSearchResultsDto> getMovies(String searchText, String countryCode) {
logger.info("GetMovies Service started");
String url = prepareUrl(searchText,countryCode);
HttpHeaders header = new HttpHeaders();
prepareHeader(header);
HttpEntity<String> requestEntity = new HttpEntity<String>(header);
try {
logger.info("Calling the API for movie info");
responseEntity = restClient.exchange(url,
HttpMethod.GET,
requestEntity,
MovieSearchResultsDto.class);
}catch (Exception e) {
logger.error("Exception occured while calling the API "+ e);
if(responseEntity.getStatusCodeValue() != 200) {
}
}
logger.info("GetMovies Service Ended");
return responseEntity;
}
And the JSON response looks like
{
"results": [
{
"id": ******,
"picture": "url",
"name": "Titanic",
"locations": [
{
"icon": "url",
"display_name": "Amazon Instant Video",
"name": "AmazonInstantVideoIVAGB",
"id": "***",
"url": "url"
}
],
"provider": "iva",
"weight": 0,
"external_ids": {
"iva_rating": null,
"imdb": {
"url": "url",
"id": "tt0046435"
},
"tmdb": {
"url": "url",
"id": "id"
},
"wiki_data": {
"url": "url",
"id": "id"
},
"iva": null,
"gracenote": null,
"rotten_tomatoes": null,
"facebook": null
}
}
] }
What I have done is , I created a class MovieSearchResultsDto and include a list as its data member with getters and setters.
private List<MoviesDto> results = new ArrayList<>();
And created MoviesDto class as below
public class MoviesDto {
private String id;
private String name;
private String picture;
#JsonInclude(value = Include.NON_EMPTY)
private List<MovieLocation> locations = new ArrayList<MovieLocation>();
#JsonInclude(value = Include.NON_EMPTY)
private List<ExternalIds> external_ids = new ArrayList<ExternalIds>();
public MoviesDto() {
}
//getters and setters
}
class MovieLocation{
private String icon;
private String id;
private String display_name;
private String name;
private String url;
public MovieLocation() {
}
//getters and setters
}
class ExternalIds{
private IdAndUrl imdb;
private IdAndUrl tmdb;
private IdAndUrl wiki_data;
public ExternalIds() {
}
//getters and setters
}
class IdAndUrl{
private String url;
private String id;
public IdAndUrl() {
}
//getters and setters
}
But it shows error while parsing.
Exception occured while calling the API org.springframework.web.client.RestClientException: Error while extracting response for type [class com.prebeesh1427.MovieNameServiceProvider.dto.MovieSearchResultsDto] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.ArrayList<com.prebeesh1427.MovieNameServiceProvider.dto.ExternalIds>` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList<com.prebeesh1427.MovieNameServiceProvider.dto.ExternalIds>` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 1054] (through reference chain: com.prebeesh1427.MovieNameServiceProvider.dto.MovieSearchResultsDto["results"]->java.util.ArrayList[0]->com.prebeesh1427.MovieNameServiceProvider.dto.MoviesDto["external_ids"])
I am a newbie to this area. Kindly help me not just only to resolve this issue but to understand the concept of these parsing techniques too.
Thanks in advance

How to ignore specific fields on income in Spring boot?

I have my domain class as follows
#Getter
#Setter
public class Student {
private Long id;
private String firstName;
private String lastName;
}
And I have this controller
#RestController
#RequestMapping("/student")
public class StudentController {
#PostMapping(consumes = "application/json", produces = "application/json")
public ResponseEntity<Student> post(#RequestBody Student student) {
//todo save student info in db, it get's an auto-generated id
return new ResponseEntity<>(student, HttpStatus.CREATED);
}
}
Now what I want is to configure serializer in a way that it ignores the id field on income, so I get only firstName and lastName, but serialize it when I'm returning the object to the caller.
Its easy to use it with jackson. There is an annotation named #JsonProperty(access = Access.READ_ONLY) where you can define if the property should be de- or serialized. Just put that annotation on your id field.
#JsonProperty(access = Access.READ_ONLY)
private Long id;
The Controller:
#PostMapping(consumes = "application/json", produces = "application/json")
public ResponseEntity<Student> post(#RequestBody Student student) {
//here we will see the that id is not deserialized
System.out.println(student.toString());
//here we set a new Id to the student.
student.setId(123L);
//in the response we will see that student will serialized with an id.
return new ResponseEntity<>(student, HttpStatus.CREATED);
}
Requestbody:
{
"id":1,
"firstName": "Patrick",
"lastName" : "secret"
}
Output of toString():
Student [id=null, firstName=Patrick, lastName=secret]
Response:
{
"id": 123,
"firstName": "Patrick",
"lastName": "secret"
}
P.S. It will also work if you dont send an id property:
{
"firstName": "Patrick",
"lastName" : "secret"
}

Expose enums with Spring Data REST

I'm using Spring Boot 1.5.3, Spring Data REST, HATEOAS.
I've a simple entity model:
#Entity
public class User extends AbstractEntity implements UserDetails {
private static final long serialVersionUID = 5745401123028683585L;
public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
#NotNull(message = "The name of the user cannot be blank")
#Column(nullable = false)
private String name;
/** CONTACT INFORMATION **/
private String landlinePhone;
private String mobilePhone;
#NotNull(message = "The username cannot be blank")
#Column(nullable = false, unique = true)
private String username;
#Email(message = "The email address is not valid")
private String email;
#JsonIgnore
private String password;
#Column(nullable = false)
private String timeZone = "Europe/Rome";
#JsonIgnore
private LocalDateTime lastPasswordResetDate;
#Column(nullable = false, columnDefinition = "BOOLEAN default true")
private boolean enabled = true;
#Type(type = "json")
#Column(columnDefinition = "json")
private Roles[] roles = new Roles[] {};
and my enum Roles is:
public enum Roles {
ROLE_ADMIN, ROLE_USER, ROLE_MANAGER, ROLE_TECH;
#JsonCreator
public static Roles create(String value) {
if (value == null) {
throw new IllegalArgumentException();
}
for (Roles v : values()) {
if (value.equals(v.toString())) {
return v;
}
}
throw new IllegalArgumentException();
}
}
I'm creating a client in Angular 4. Spring Data REST is great and expose repository easily return my model HATEOAS compliant:
{
"_embedded": {
"users": [
{
"name": "Administrator",
"username": "admin",
"roles": [
"Amministratore"
],
"activeWorkSession": "",
"_links": {
"self": {
"href": "http://localhost:8080/api/v1/users/1"
},
"user": {
"href": "http://localhost:8080/api/v1/users/1{?projection}",
"templated": true
}
}
},
Like you can see I'm also translating via rest-messages.properties the value of my enums. Great!
My Angular page now needs the complete lists of roles (enums). I've some question:
understand the better way for the server to return the list of roles
how to return this list
My first attemp was to create a RepositoryRestController in order to take advantage of what Spring Data REST offers.
#RepositoryRestController
#RequestMapping(path = "/api/v1")
public class UserController {
#Autowired
private EntityLinks entityLinks;
#RequestMapping(method = RequestMethod.GET, path = "/users/roles", produces = "application/json")
public Resource<Roles> findRoles() {
Resource<Roles> resource = new Resource<>(Roles.ROLE_ADMIN);
return resource;
}
Unfortunately, for some reason, the call to this methods return a 404 error. I debugged and the resource is created correctly, so I guess the problem is somewhere in the JSON conversion.
how to return this list?
#RepositoryRestController
#RequestMapping("/roles")
public class RoleController {
#GetMapping
public ResponseEntity<?> getAllRoles() {
List<Resource<Roles>> content = new ArrayList<>();
content.addAll(Arrays.asList(
new Resource<>(Roles.ROLE1 /*, Optional Links */),
new Resource<>(Roles.ROLE2 /*, Optional Links */)));
return ResponseEntity.ok(new Resources<>(content /*, Optional Links */));
}
}
I was playing around with this and have found a couple of ways to do it.
Assume you have a front end form that wants to display a combo box containing priorities for a single Todo such as High, Medium, Low. The form needs to know the primary key or id which is the enum value in this instance and the value should be the readable formatted value the combo box should display.
If you wish to customize the json response in 1 place only such as a single endpoint then I found this useful. The secret sauce is using the value object PriorityValue to allow you to rename the json field through #Relation.
public enum Priority {
HIGH("High"),
NORMAL("Normal"),
LOW("Low");
private final String description;
Priority(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static List<Priority> orderedValues = new ArrayList<>();
static {
orderedValues.addAll(Arrays.asList(Priority.values()));
}
}
#RepositoryRestController
#RequestMapping(value="/")
public class PriorityController {
#Relation(collectionRelation = "priorities")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
private class PriorityValue {
private String id;
private String value;
public PriorityValue(String id,
String value) {
this.id = id;
this.value = value;
}
}
#GetMapping(value = "/api/priorities", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Resources<PriorityValue>> getPriorities() {
List<PriorityValue> priorities = Priority.orderedValues.stream()
.map(p -> new PriorityValue(p.name(), p.getDescription()))
.collect(Collectors.toList());
Resources<PriorityValue> resources = new Resources<>(priorities);
resources.add(linkTo(methodOn(PriorityController.class).getPriorities()).withSelfRel());
return ResponseEntity.ok(resources);
}
}
Another approach is to use a custom JsonSerializer. The only issue using this is everywhere a Priority enum is serialized you will end up using this format which may not be what you want.
#JsonSerialize(using = PrioritySerializer.class)
#Relation(collectionRelation = "priorities")
public enum Priority {
HIGH("High"),
NORMAL("Normal"),
LOW("Low");
private final String description;
Priority(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public static List<Priority> orderedValues = new ArrayList<>();
static {
orderedValues.addAll(Arrays.asList(Priority.values()));
}
}
#RepositoryRestController
#RequestMapping(value="/api")
public class PriorityController {
#GetMapping(value = "/priorities", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Resources<Priority>> getPriorities() {
Resources<Priority> resources = new Resources<>(Priority.orderedValues);
resources.add(linkTo(methodOn(PriorityController.class).getPriorities()).withSelfRel());
return ResponseEntity.ok(resources);
}
}
public class PrioritySerializer extends JsonSerializer<Priority> {
#Override
public void serialize(Priority priority,
JsonGenerator generator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("id");
generator.writeString(priority.name());
generator.writeFieldName("value");
generator.writeString(priority.getDescription());
generator.writeEndObject();
}
}
The final json response from http://localhost:8080/api/priorities
{
"_embedded": {
"priorities": [
{
"id": "HIGH",
"value": "High"
},
{
"id": "NORMAL",
"value": "Normal"
},
{
"id": "LOW",
"value": "Low"
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/priorities"
}
}
}

GSON,AndroidAnnotations - Expected BEGIN_OBJECT but was String

I searched similar topics but none of them helped me.
My JSON response is:
{
"success": "true",
"data": {
"id": "x",
"user_name": "xxx",
"email": "xxx#xxx.com",
"first_name": "xxx",
"last_name": "xx",
"position": "xxx",
"session_id": "xxx"
}
}
My Java classes are:
Response:
public class Response {
public String success;
public Data data;
public Response() {
}
public Response(String success, Data data) {
this.success = success;
this.data = data;
}
}
Data
public class Data {
public String id;
public String user_name;
public String email;
public String first_name;
public String last_name;
public String position;
public String session_id;
public Data() {
}
public Data(String id, String user_name, String email, String first_name, String last_name, String position, String session_id) {
this.id = id;
this.user_name = user_name;
this.email = email;
this.first_name = first_name;
this.last_name = last_name;
this.position = position;
this.session_id = session_id;
}
}
I am using android annotations to establish rest connection.
My RestClient is:
#Rest(rootUrl = "http://xxx/services", converters = {GsonHttpMessageConverter.class})
public interface MyRestClient {
#Post("/login.php")
ResponseEntity<Response> login(User user);
RestTemplate getRestTemplate();
void setRestTemplate(RestTemplate restTemplate);
}
And in main activity I use:
ResponseEntity<Response> resp = restCli.login(new User("xxx","xxx"));
I get an error
Expected BEGIN_OBJECT but was String at line 1 column 4
I tried to change 'success' filed type to boolean,Boolean i Java class - didn't help.
I tried changing the method return type in the rest interface to void and then no error, so I think the error is connected with wrong response class, but I have no idea what is wrong. Could you help me?

Resources