micronaut-projects / micronaut-projects/micronaut-problem-json

Deserialization Does not work with Jackson and Kotlin

Open
#413 1 comment 0 reactions 0 assignees View on GitHub
info: good first issue type: enhancement
Dominant language
Java
Stars
14
Forks
9
Avg merge
7h 59m
Merged PRs (30d)
5

Description

I noticed [all tests cast the body as a map.](https://github.com/micronaut-projects/micronaut-problem-json/blob/3.3.x/test-suite-groovy/src/test/groovy/io/micronaut/problem/OutOfStockSpec.groovy#L37) So maybe this is a new feature request?

Possible issues, but curious if the databind should be included by micronaut problem at least

https://github.com/zalando/problem/issues/505

https://github.com/zalando/problem/issues/485

https://github.com/zalando/problem/issues/145

### Expected Behavior

Without any additional dependencies, I'd expect http client calls with Problem type deserialization to work without Micronaut serde (I'm not sure if micronaut serde works either because switching to it broke too many things, will need to isolate)

Default Problem response

```json
{"type":"about:blank","parameters":{},"status":404,"title":"Test Action does not exist"}
```

Constraint Violation Problem Response

```json
{"violations":[{"field":"createScheduledAction.scheduledAction.scheduledTime","message":"must be a date in the present or in the future"}],"type":"https://zalando.github.io/problem/constraint-violation","status":400,"title":"Constraint Violation"}
```

Code

```kotlin
httpClient.toBlocking().exchange(HttpRequest.GET("v1/scheduled-action/${UUID.randomUUID()}"),
argumentOf(), argumentOf()
```

```kotlin
httpClient.toBlocking().exchange(HttpRequest.GET("v1/scheduled-action/${UUID.randomUUID()}"),
argumentOf(), argumentOf()
```

> [!NOTE]
> Spring has [ConstraintViolationProblemModule](https://github.com/zalando/problem-spring-web/blob/main/problem-violations/src/main/java/org/zalando/problem/violations/ConstraintViolationProblemModule.java) in addition to the `ProblemModule()`

### Actual Behaviour

There are 2 Actual Behaviors based on an extra dependency added.

The Micronaut Object Mapper does not have `ProblemModule()` Registered. I had to include https://mvnrepository.com/artifact/org.zalando/jackson-datatype-problem/0.27.1

```kotlin
implementation("org.zalando:jackson-datatype-problem:0.27.1")
```

Default Problem response

```json
{"type":"about:blank","parameters":{},"status":404,"title":"Test Action does not exist"}
```

Constraint Violation Problem Response

```json
{"violations":[{"field":"createScheduledAction.scheduledAction.scheduledTime","message":"must be a date in the present or in the future"}],"type":"https://zalando.github.io/problem/constraint-violation","status":400,"title":"Constraint Violation"}
```

## Without the Zalando Jackson DataTyp Dependency

### Default Problem
```
08:40:06.404 [default-nioEventLoopGroup-1-3] DEBUG io.micronaut.http.client.netty.DefaultHttpClient - Error decoding HTTP error response body: Error decoding JSON stream for type [defaultProblem]: Cannot construct instance of `org.zalando.problem.StatusType` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 48]
io.micronaut.http.codec.CodecException: Error decoding JSON stream for type [defaultProblem]: Cannot construct instance of `org.zalando.problem.StatusType` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 48]
```

### ConstraintViolationThrowableProblem

```
07:16:24.457 [default-nioEventLoopGroup-1-3] DEBUG io.micronaut.http.client.netty.DefaultHttpClient - Error decoding HTTP error response body: Error decoding JSON stream for type [constraintViolationThrowableProblem]: Cannot construct instance of `org.zalando.problem.StatusType` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 4195] (through reference chain: io.micronaut.problem.violations.ConstraintViolationThrowableProblem["status"])
io.micronaut.http.codec.CodecException: Error decoding JSON stream for type [constraintViolationThrowableProblem]: Cannot construct instance of `org.zalando.problem.StatusType` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 4195] (through reference chain: io.micronaut.problem.violations.ConstraintViolationThrowableProblem["status"])
```

## With the Zalando Jackson Datatype Dependency

### Default Problem

```
08:41:06.237 [io-executor-thread-1] ERROR io.micronaut.http.server.RouteExecutor - Unexpected error occurred: Error encoding object [io.micronaut.problem.ProblemErrorResponseProcessor$IntrospectedThrowableProblemWithoutStacktrace@1278937c] to JSON: Problem with definition of [AnnotedClass io.micronaut.problem.ProblemErrorResponseProcessor$IntrospectedThrowableProblemWithoutStacktrace]: Multiple 'any-getter' methods defined ([method io.micronaut.problem.ProblemErrorResponseProcessor$IntrospectedThrowableProblemWithoutStacktrace#getProperties()] vs [method io.micronaut.problem.ProblemErrorResponseProcessor$IntrospectedThrowableProblemWithoutStacktrace#getParameters()])
io.micronaut.http.codec.CodecException: Error encoding object [io.micronaut.problem.ProblemErrorResponseProcessor$IntrospectedThrowableProblemWithoutStacktrace@1278937c] to JSON: Problem with definition of [AnnotedClass io.micronaut.problem.ProblemErrorResponseProcessor$IntrospectedThrowableProblemWithoutStacktrace]: Multiple 'any-getter' methods defined ([method io.micronaut.problem.ProblemErrorResponseProcessor$IntrospectedThrowableProblemWithoutStacktrace#getProperties()] vs [method io.micronaut.problem.ProblemErrorResponseProcessor$IntrospectedThrowableProblemWithoutStacktrace#getParameters()])
```

### ConstraintViolationThrowableProblem

```
08:41:52.372 [default-nioEventLoopGroup-1-3] DEBUG io.micronaut.http.client.netty.DefaultHttpClient - Error decoding HTTP error response body: Error decoding JSON stream for type [constraintViolationThrowableProblem]: Could not resolve type id 'https://zalando.github.io/problem/constraint-violation' as a subtype of `io.micronaut.problem.violations.ConstraintViolationThrowableProblem`: known type ids = [ConstraintViolationThrowableProblem]
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 147]
io.micronaut.http.codec.CodecException: Error decoding JSON stream for type [constraintViolationThrowableProblem]: Could not resolve type id 'https://zalando.github.io/problem/constraint-violation' as a subtype of `io.micronaut.problem.violations.ConstraintViolationThrowableProblem`: known type ids = [ConstraintViolationThrowableProblem]
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 147]
```

### Steps To Reproduce

Just so I don't have to parse out the body from the exception, I did this with `application-test.yaml`. But it does not affect the behavior, I get the same problem either way, this just seems nicer for tests as it feels more like REST assured.

```yaml
micronaut:
http:
client:
exception-on-error-status: false
```

### Environment Information

- Java 17
- Kotlin 1.9.25

```kotlin
implementation(mn.micronaut.kotlin.extension)
implementation(mn.micronaut.jackson.databind)
implementation(mn.micronaut.problem.json)
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
```

### Example Application

_No response_

### Version

4.7.2

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the Kotlin HTTP client calls using application-test.yaml and the listed Micronaut and Zalando dependencies. Inspect ProblemErrorResponseProcessor and the object-mapper integration around DefaultProblem and ConstraintViolationThrowableProblem. Done means both problem responses deserialize without the extra Jackson datatype dependency and server encoding no longer reports the multiple any-getter conflict.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, kotlin
Domain
api, backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.