Recently I am using Spring boot framework with Kotlin. Every thing is all okey with GET method. But when registering a new user with POST method I go faced some problem having Bad Request with status code 400.
Here is my code associate with my spring boot project
User.kt
#Entity
#Table(name = "user_info")
data class User(
#Id
#SequenceGenerator(
name = "user_seq",
sequenceName = "user_seq",
allocationSize = 1
)
#GeneratedValue(
strategy = SEQUENCE,
generator = "user_seq"
)
#Column(
name = "id",
updatable = false
)
val id: Long = -1,
#Column(
name = "first_name",
nullable = false,
length = 50,
updatable = true
)
val firstName: String,
#Column(
name = "last_name",
nullable = false,
length = 50,
updatable = true
)
val lastName: String,
#Column(
name = "email",
nullable = true,
length = 150,
updatable = true
)
val email: String,
#Column(
name = "gender",
nullable = false,
length = 2,
updatable = true
)
val gender: String,
#Column(
name = "date_of_birth",
nullable = false,
updatable = true
)
val dateOfBirth: LocalDate,
#Column(
name = "country",
nullable = false,
length = 50,
updatable = true
)
val country: String
)
UserController.kt
#RestController
#RequestMapping(
path = [
"/api/v1/"
]
)
class UserController(
#Autowired private val userService: UserService
) {
#PostMapping("register")
fun registerUser(#RequestBody user: User) {
userService.registerUser(user)
}
#GetMapping("users")
fun getUsers(): List<User> {
return userService.getUsers()
}
#GetMapping("user/{id}")
fun getUsers(#PathVariable("id") id: Long): User {
return userService.getUserInfo(id)
}
}
My Request Payload is
POST http://localhost:8080/api/v1/register
Content-Type: application/json
{
"first_name" : "Abid",
"last_name" : "Affan",
"email" : "aminul15-5281#diu.edu.bd",
"gender" : "M",
"date_of_birth" : "2019-05-03",
"country" : "Bangladesh"
}
and my response payload is
POST http://localhost:8080/api/v1/register
HTTP/1.1 400
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 01 Mar 2021 05:52:03 GMT
Connection: close
{
"timestamp": "2021-03-01T05:52:03.634+00:00",
"status": 400,
"error": "Bad Request",
"message": ""JSON parse error: Instantiation of [simple type, class com.example.demo.user.User] value failed for JSON property firstName due to missing (therefore NULL) value for creator parameter firstName which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.example.demo.user.User] value failed for JSON property firstName due to missing (therefore NULL) value for creator parameter firstName which is a non-nullable type\n at [Source: (PushbackInputStream); line: 8, column: 1] (through reference chain: com.example.demo.user.User[\"firstName\"])",
"path": "/api/v1/register"
}
Response code: 400; Time: 214ms; Content length: 119 bytes
Add following property in your spring boot configuration
logging.level.org.springframework.web=DEBUG you will get the exact reason for getting 400 Bad requests on console logs.
You used camelCase in your entity - User and snake_case in your request payload. It is recommended not to mix syntax for object mapping. Can you try with this request poayload:
{
"firstName" : "Abid",
"lastName" : "Affan",
"email" : "aminul15-5281#diu.edu.bd",
"gender" : "M",
"dateOfBirth" : "2019-05-03",
"country" : "Bangladesh"
}
Related
I have model class as follows :
public class ItemAdditionalInfo extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull(groups = Existing.class)
#Null(groups = New.class)
private Long itemAdditionalInfoId;
#NotNull
#Column(nullable = false)
private Integer itemNbr;
private String itemDescription;
private Integer mdsFamId;
private Integer deptNbr;
private String deptDesc;
private Integer subcatNbr;
private String subcatDesc;
private Double retailPrice;
etc...
}
public class DecileConfig extends BaseEntity {
#Id
#GeneratedValue(generator = "uuid")
#Null(groups = New.class)
private String id;
private Boolean decileMode;
private String decileCalculatorType;
private String decileLevelType;
private Integer decileLevelNumber;
private Integer forecastRange;
etc...
}
public class ItemAdditionalDecileGetDTO {
private ItemAdditionalInfo itemAdditionalInfo;
private DecileConfig decileConfig;
}
Now while testing... I am supposed to get ItemAdditionalDecileGetDTO object and I need test some of its fields in ItemAdditionalInfo.
So I wrote something like this :
MvcResult result = mockMvc.perform(requestBuilder)
.andReturn();
MockHttpServletResponse response = result.getResponse();
ServiceResponse sr =gson.fromJson(response.getContentAsString(), ServiceResponse.class);
System.out.println("SR Obj "+sr);
List<ItemAdditionalDecileGetDTO> responeList = (List<ItemAdditionalDecileGetDTO>)sr.getData();//even though its type casted but its actually gson.LinkedTreeMap
String json = gson.toJsonTree(responeList.get(0)).getAsJsonObject().toString();
System.out.println("Json Obj "+json);
ItemAdditionalDecileGetDTO obj = gson.fromJson(json,ItemAdditionalDecileGetDTO.class);
System.out.println("Obtained IAdInfo "+obj);
assertEquals(123, java.util.Optional.of(obj.getItemAdditionalInfo().getItemNbr()));
I got ServiceResponse as Json as follows from this line "System.out.println("Json Obj "+json);":
{
"data": [
{
"itemAdditionalInfo": {
"createdBy": "s0r0e4g",
"createdOn": "2022-08-26T00:00:00Z",
"lastUpdatedBy": "admin",
"lastUpdatedOn": "2023-01-19T00:00:00Z",
"itemAdditionalInfoId": 9999,
"itemNbr": 123,
"itemDescription": "code01 23",
"mdsFamId": null,
"deptNbr": 202,
"deptDesc": null,
"subcatNbr": 100,
"subcatDesc": null,
"retailPrice": null,
"events": [
{
"end_date": "2022-12-24",
"event_name": "FY23 December ISB",
"event_status": "Past",
"start_date": "2022-11-30"
}
],
"imported": false,
"itemStatus": null,
"vendorNbrFull": null,
"originZipCode": null,
"vendorCommittedQty": null,
"vendorRemainingQty": null,
"palletTiQty": null,
"palletHiQty": null,
"vendorIncrementOrderQty": null,
"vendorPackQty": null,
"isBreakPack": null,
"histLikeItemNbr": null,
"secondaryLikeItemNbrs": null,
"inheritCategoryDecile": false,
"inheritSubcategoryDecile": true,
"isVLTRequired": true,
"isClubLevelParamPresent": true,
"overrideQty": null,
"minOrderQty": null,
"demandMultiplier": null,
"itemOnOverrideDate": null,
"itemOffOverrideDate": null,
"overrideQtyStatus": null,
"isRunning": null,
"maxOffShelfDate": "2023-02-01",
"minOnShelfDate": "2007-09-10",
"totalOnHand": null,
"totalOnOrder": null,
"leadTime": null,
"clubCountCdf": null,
"clubCountLegacy": null,
"sumNeedQtyCdf": null,
"sumNeedQtyLegacy": 2,
"totalDemandLegacy": null,
"allocationStatus": "Allocation available",
"allocationDescription": null,
"tenantId": "sams_us",
"seasonName": null,
"likeItemsWithEstimatedMultiplier": null
},
"decileConfig": {
"createdBy": "s0k06wm",
"createdOn": "2022-01-13T00:00:00Z",
"lastUpdatedBy": "admin",
"lastUpdatedOn": "2023-01-18T00:00:00Z",
"id": "63081ab2d6e1a61d443d8483",
"decileMode": false,
"decileCalculatorType": "test",
"decileLevelType": "subcat-202",
"decileLevelNumber": 100,
"forecastRange": 4,
"decileCalculatorParameters": [
{
"decileCalculatorParameterKey": "sales",
"decileCalculatorParameterValue": "50"
},
{
"decileCalculatorParameterKey": "profit",
"decileCalculatorParameterValue": "50"
},
{
"decileCalculatorParameterKey": "from",
"decileCalculatorParameterValue": "2021-01-01"
},
{
"decileCalculatorParameterKey": "to",
"decileCalculatorParameterValue": "2021-03-01"
}
],
"decileConfiguration": null,
"globalDecileConfiguration": {
"decileNo": "1",
"minPres": "1",
"wosTarget": "2",
"maxClubQty": "5",
"oosThreshold": "0"
},
"tenantId": "sams_us"
}
}
],
"errors": null,
"metadata": null,
"status": null,
"pageable": {
"totalPages": 1,
"totalElements": 1,
"pageSize": 10,
"pageNumber": 0,
"numberOfElements": 1
}
}
But I got this exception :
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:
Expected BEGIN_OBJECT but was STRING at line 1 column 59 path
$.itemAdditionalInfo.createdOn.
As it could not parse this line :
ItemAdditionalDecileGetDTO obj = gson.fromJson(json,ItemAdditionalDecileGetDTO.class);
FYI createdOn and lastUpdatedOn is being populated by DB and I have no control over it and its a DateTime of type.
I've generated endpoint using openapi 3.0 that consumes form data. No idea what could have I done wrong, since its all generated and in past I've been able to upload file like this. The difference is that now I have multiple fields other than file.
paths:
/movie:
post:
operationId: createMovie
description: creates movie
requestBody:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/MovieRequest'
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Movie'
Movie request component:
MovieRequest:
type: object
properties:
title:
type: string
description:
type: string
director:
type: string
length:
type: integer
format: int64
category:
$ref: '#/components/schemas/Category'
ageCategory:
$ref: '#/components/schemas/AgeCategory'
poster:
type: string
format: binary
trailerLink:
type: string
shortDescription:
type: string
Generated controller:
#ApiOperation(value = "", nickname = "createMovie", notes = "creates movie", response = MovieModelApi.class, tags={ })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "", response = MovieModelApi.class) })
#RequestMapping(
method = RequestMethod.POST,
value = "/movie",
produces = { "application/json" },
consumes = { "multipart/form-data" }
)
default ResponseEntity<MovieModelApi> createMovie(#ApiParam(value = "") #Valid #RequestPart(value = "title", required = false) String title,#ApiParam(value = "") #Valid #RequestPart(value = "description", required = false) String description,#ApiParam(value = "") #Valid #RequestPart(value = "director", required = false) String director,#ApiParam(value = "", allowableValues = "HORROR") #Valid #RequestPart(value = "category", required = false) CategoryModelApi category,#ApiParam(value = "", allowableValues = "PG13") #Valid #RequestPart(value = "ageCategory", required = false) AgeCategoryModelApi ageCategory,#ApiParam(value = "") #Valid #RequestPart(value = "poster", required = false) MultipartFile poster) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"director\" : \"director\", \"isEnabled\" : true, \"description\" : \"description\", \"id\" : 5, \"title\" : \"title\", \"poster\" : \"poster\" }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
After importing the yaml to postman I send request:
That contains headers:
But I get 415 unsupported media error
HTTP 415 Unsupported Media Type indicates that the server is refusing to accept the request because the payload format is not supported. The format problem may be related to the Content-Type or Content-Encoding specified in the request, or as a result of direct validation of the data.
I have 3 collections: attributes, products and product_attributes. I am trying to retrieve product attributes with aggregation with nested attribute and product in it. But product and attribute always null
service method:
fun findById(id: String): Mono<ProductAttribute> {
val idField = "_id"
val productId = "productId"
val attributeId = "attributeId"
val attributesDb = "attributes"
val productsDb = "products"
val attributeName = "attribute"
val productName = "product"
fun lookup(from: String, localField: String, name: String) = LookupOperation.newLookup()
.from(from)
.localField(localField)
.foreignField(idField)
.`as`(name)
fun match(id: String) = Aggregation.match(Criteria.where(idField).`is`(ObjectId(id)))
fun unwind(field: String) = Aggregation.unwind(field)
val aggregation = Aggregation.newAggregation(
match(id),
lookup(productsDb, productId, productName),
unwind(productName),
lookup(attributesDb, attributeId, attributeName),
unwind(attributeName)
).withOptions(AggregationOptions.builder().allowDiskUse(true).build())
return operations.aggregate(aggregation, "product_attributes", ProductAttribute::class.java)
.last()
.doOnError { throwable -> logger.error("Failed to get productAttribute", throwable) }
}
ProductAttribute.kt
#Document(collection = "product_attributes")
data class ProductAttribute(
#Id
#JsonProperty("_id")
val id: String? = ObjectId().toHexString(),
#get:Transient #Value("null") val product: Product?,
#get:Transient #Value("null") val attribute: Attribute?,
val attributeId: String,
val productId: String,
val name: String,
val quantity: Int,
val photos: List<String>
) : Serializable
Response:
{
"data": {
"productAttributeId": {
"id": "62a3bff787418b6f837e9150",
"product": null,
"attribute": null,
"name": "string",
"quantity": 1,
"photos": [
"string"
]
}
}
}
As a workaround I have ended up with this result:
I made aggregate method with generic type. It will aggregate to Any type and then map to my specific type.
inline fun <reified T> aggregate(
aggregation: Aggregation,
collectionName: String,
operations: ReactiveMongoOperations,
objectMapper: ObjectMapper
): Flux<T> =
operations.aggregate(aggregation, collectionName, Any::class.java)
.map { data -> objectMapper.convertValue(data, T::class.java) }
I think there is a way to implement custom converter. I will think about this later. I will update answer on success
i develop API using Spring Boot and JPA
I found a case in Bidirectional OneToMany relationship, my parent ID doesn't save in DB when requested by JSON payload like this :
{
"name": "Rice",
"description": "Lorem ipsum dolor sit amet",
"variants": [
{
"amount": 1,
"unit": "kg",
"price": 60000
}
]
}
Here is my Model :
Product.kt
data class Product(
#Id
#GeneratedValue
var id: Long = 0,
var name: String = "",
var photo: String = "",
var description: String = "",
#OneToMany(mappedBy = "product", cascade = [CascadeType.ALL])
#JsonBackReference
var variants: MutableList<Variant> = mutableListOf()
)
Variant.kt
#Entity
data class Variant(
#Id
#GeneratedValue
var id: Long = 0,
var amount: Int = 0,
var unit: String = "",
var price: Double = 0.0,
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id")
#JsonManagedReference
var product: Product? = null
)
Is there any other configuration need to be applied?
Thank you
The problem is, that during JSON deserialization the back reference from Variant to Product is not set.
And you define
#OneToMany(mappedBy = "product", cascade = [CascadeType.ALL])
#JsonBackReference
var variants: MutableList<Variant> = mutableListOf()
mappedBy says that the back reference is the owning side and therefor in charge for setting the foreign key.
You have two options:
Either you set the product reference in Variant before saving the Product
or you remove the back reference from Variant to Product and remove the mappedBy = "product" from the variants collection.
I'm testing Spring Data REST.
I was following this tutorial to understand how it works.
The code so far is simple as:
#Entity
#SequenceGenerator(name="my_seq", sequenceName="user_id_seq")
#Table(name = "employees")
data class Employee (#Id #GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq") val id: Long? = null,
val name: String = "defConstructorHell")
and when I ask with GET I obtain the following json:
{
"_embedded" : {
"employees" : [ {
"name" : "ciao",
"_links" : {
"self" : {
"href" : "http://localhost:5000/api/employees/1"
},
"employee" : {
"href" : "http://localhost:5000/api/employees/1"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:5000/api/employees{?page,size,sort}",
"templated" : true
},
"profile" : {
"href" : "http://localhost:5000/api/profile/employees"
}
},
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
Which is totally fine but I'm using a js framework that requires the header X-Total-Count.
Do you know if there is a way of doing it wi Spring Data REST?
Eventually I added a controller that add the requested header:
#RepositoryRestController
class EmployeeController #Autowired constructor(val repo: EmployeeRepository) {
#RequestMapping(method = arrayOf(GET),
path = arrayOf("employees"))
#ResponseBody
fun getEmployees(#RequestParam("_sort", required = false, defaultValue = "id") _sort: String,
#RequestParam("_order", required = false, defaultValue = "DESC") _order: String,
#RequestParam("_start", required = false, defaultValue = "0") _start: Int,
#RequestParam("_end", required = false, defaultValue = "20") _end: Int): ResponseEntity<MutableIterable<Employee>> {
val pr = PageRequest(_start, 20, Sort.Direction.valueOf(_order), _sort)
val result = repo.findAll(pr)
val headers = HttpHeaders()
headers.add("X-Total-Count", result.count().toString())
return ResponseEntity(result.content, headers, OK)
}
}