Home > Software engineering >  @Requestbody with List of objects with numbers initialized to 0
@Requestbody with List of objects with numbers initialized to 0

Time:11-25

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"
        }
    ]
}
  • Related