In a @PostMapping call, when a list of objects is received via the @RequestBody. And this list contains Int or Double variables, if these variables are not sent in the request body json, the variables are self-initialized to 0. Instead of this, I understand that it should return bad request
This problem does not happen with the BigDecimal for example and returns bad request with this variables, or if the body of the request is an object instead of a list.
Do you know how to solve this? is it a spring problem?
Example to reproduce the problem:
data class Animal(
val name: String,
val height: Double
)
@PostMapping("/animals")
suspend fun saveAnimals(
@RequestBody request: List<Animal>
): ResponseEntity<Any> {
println(request[0].height)
return ResponseEntity.ok().build()
}
In the example above the print result will be 0 if the height is not sent on the request, but I expected this to return a bad request.
CodePudding user response:
You have to declare your DTO as
data class Animal(
val name: String,
val height: Double? // <-----see the ?
)
val height: Double
is translated in JVM
into primitive double
which has as initial value if not defined the 0.0
.
val height: Double?
however is translated in JVM
into the boxed type Double
which has as intial value if not defined the null
.
CodePudding user response:
Because Kotlin doesn't use primitive and wrapper object like Java. Example: int
and Integer
in Java. If kotlin can optimize, so it will do. So this Double
will be double
in built version. Ofc, if u call wrapper method, use any genetic (example List), or the attribute is nullable type in your code, Kotlin won't change to primitive double.
In summary: primitive double default value is 0.0
IDEA Java code example from data class Animal
:
// Animal.java
package com.example.demo2;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Metadata(
mv = {1, 6, 0},
k = 1,
d1 = {"\u0000(\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\u0006\n\u0002\b\t\n\u0002\u0010\u000b\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0002\b\u0086\b\u0018\u00002\u00020\u0001B\u0015\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0005¢\u0006\u0002\u0010\u0006J\t\u0010\u000b\u001a\u00020\u0003HÆ\u0003J\t\u0010\f\u001a\u00020\u0005HÆ\u0003J\u001d\u0010\r\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u0005HÆ\u0001J\u0013\u0010\u000e\u001a\u00020\u000f2\b\u0010\u0010\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0011\u001a\u00020\u0012HÖ\u0001J\t\u0010\u0013\u001a\u00020\u0003HÖ\u0001R\u0011\u0010\u0004\u001a\u00020\u0005¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\bR\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\t\u0010\n¨\u0006\u0014"},
d2 = {"Lcom/example/demo2/Animal;", "", "name", "", "height", "", "(Ljava/lang/String;D)V", "getHeight", "()D", "getName", "()Ljava/lang/String;", "component1", "component2", "copy", "equals", "", "other", "hashCode", "", "toString", "demo2"}
)
public final class Animal {
@NotNull
private final String name;
private final double height;
@NotNull
public final String getName() {
return this.name;
}
public final double getHeight() {
return this.height;
}
public Animal(@NotNull String name, double height) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
this.height = height;
}
@NotNull
public final String component1() {
return this.name;
}
public final double component2() {
return this.height;
}
@NotNull
public final Animal copy(@NotNull String name, double height) {
Intrinsics.checkNotNullParameter(name, "name");
return new Animal(name, height);
}
// $FF: synthetic method
public static Animal copy$default(Animal var0, String var1, double var2, int var4, Object var5) {
if ((var4 & 1) != 0) {
var1 = var0.name;
}
if ((var4 & 2) != 0) {
var2 = var0.height;
}
return var0.copy(var1, var2);
}
@NotNull
public String toString() {
return "Animal(name=" this.name ", height=" this.height ")";
}
public int hashCode() {
String var10000 = this.name;
return (var10000 != null ? var10000.hashCode() : 0) * 31 Double.hashCode(this.height);
}
public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Animal) {
Animal var2 = (Animal)var1;
if (Intrinsics.areEqual(this.name, var2.name) && Double.compare(this.height, var2.height) == 0) {
return true;
}
}
return false;
} else {
return true;
}
}
}
CodePudding user response:
In addition to my other answer, another concept:
Why not work validation via List: https://stackoverflow.com/a/35643761/2625393
Work with this:
implementation("org.springframework.boot:spring-boot-starter-validation:2.7.5")
data class ListAnimal(
@field:Valid
val list: List<Animal>
)
data class Animal(
val name: String,
@field:NotNull
val height: Double?
)
@RestController
class Controller {
@PostMapping("/animals")
suspend fun saveAnimals(@RequestBody @Valid request: ListAnimal): ResponseEntity<Any> {
println(request.list)
return ResponseEntity.ok().build()
}
}
POST http://localhost:8080/animals
Content-Type: application/json
{
"list": [
{
"name": "name"
}
]
}