javaspring-bootjsonbspring-restjsonb-api

@JsonbTypeAdapter is not used by Spring Boot


I am trying to use Json-B with Spring Boot version: 2.3.1

Json-b

<dependency>
            <groupId>javax.json.bind</groupId>
            <artifactId>javax.json.bind-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse</groupId>
            <artifactId>yasson</artifactId>
            <version>1.0.5</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.json</artifactId>
            <version>1.1.4</version>
        </dependency>

The Rest Controller:

@RestController
public class MyController {

    @PostMapping("/api/test")
    public ResponseEntity init(@Valid @RequestBody MyRequest request) {
        return ResponseEntity.ok("ok");
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            final Object rejectedValue = ((FieldError) error).getRejectedValue();
            errors.put("Rejected Value: ", rejectedValue.toString());
            errors.put(fieldName, errorMessage);
        });
        return errors;
    }


}

The request:


public class MyRequest  {

    @DurationMin(minutes = 1")
    @DurationMax(minutes = 10)
    @JsonbTypeAdapter(DurationAdapter.class)
    private Duration expiredTime;

    ....getter setter... 
}

The Json-B Adapter:

public class DurationAdapter implements JsonbAdapter<Duration, JsonValue> {

    @Override
    public JsonValue adaptToJson(Duration duration) throws Exception {
        final JsonNumber value = Json.createValue(duration.toMillis());
        return value;
    }

    @Override
    public Duration adaptFromJson(JsonValue jsonValue) throws Exception {
        final long timeInMillis = ((JsonNumber) jsonValue).longValue();
        return Duration.ofMillis(timeInMillis);
    }
}

I also tried this adapter as well:

public class DurationAdapter implements JsonbAdapter<Duration, Long> {

    @Override
    public Long adaptToJson(Duration obj) throws Exception {
        return obj.toMillis();
    }

    @Override
    public Duration adaptFromJson(Long obj) throws Exception {
        return Duration.ofMillis(obj);
    }
}

I used Postman to send a post request with content like this:

{
    "expiredTime": 6000
}

I put break points in my adapter but the app never ran through the break points. I put a break points at the rejected value in the exception handler in the rest controller, and I saw that the duration was always in "seconds", not milliseconds. If the request has "expiredTime":6000, then the Java object will have expriredTime field with value Duration=6000 seconds (use debugger to see the value) and an exception will be thrown (that's why I can use the debugger at the exception handler).

How can I make Spring boot use my Json-B Adapter? Thank you!

Updated: I also have specified this in application.properties: spring.http.converters.preferred-json-mapper=jsonb


Solution

  • Your code works, I prepared another example to show here. I excluded jackson like below just to make sure it is not used.

            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
    

    and used println to show in the log. and looks like this

    enter image description here

    as far as I understand spring.http.converters.preferred-json-mapper is not very effective. As long as it finds any mapper in class path spring uses that. And in https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html there is no http.converter