AdapterCodec does not prioritize decoders/encoders
Currently the decoders/encoders are added to the AdapterCodec in the order they are listed in the builder. That leads to the fact that
AdapterCodec adapterCodec = AdapterCodec.newBuilder()
.decoder(JacksonAdapterFactory.createJsonDecoder(new JsonMapper())) // only kicks in for content-type application/json
.basic()
.build();
client = Methanol.newBuilder()
.adapterCodec(adapterCodec)
HttpRequest request = ...;
HttpResponse<ResponsePayload> response = client.send(request, ResponsePayload.class);
try (ResponsePayload payload = response.body()) {
if (HttpStatus.isSuccessful(response)) {
// successful response, just return the body
return payload.to(SomeOtherJsonAnnotatedClass.clas);
} else {
throw new IOException("Error");
}
}
throws the following exception for a non-success response with no content type header being set.
java.io.IOException: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Rate': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 5]
at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:572)
at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
at com.github.mizosoft.methanol.Methanol$InterceptorChain.forward(Methanol.java:1144)
at com.github.mizosoft.methanol.Methanol$RewritingInterceptor.intercept(Methanol.java:1190)
at com.github.mizosoft.methanol.Methanol$InterceptorChain.forward(Methanol.java:1145)
at com.github.mizosoft.methanol.Methanol.send(Methanol.java:440)
at com.github.mizosoft.methanol.Methanol.send(Methanol.java:422)
...
Caused by: java.io.UncheckedIOException: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Rate': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 5]
at com.github.mizosoft.methanol.adapter.jackson.JacksonAdapter$TextFormatDecoder.readValueUnchecked(JacksonAdapter.java:170)
at com.github.mizosoft.methanol.adapter.jackson.JacksonAdapter$AbstractDecoder.lambda$toObject$0(JacksonAdapter.java:135)
at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:642)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2079)
at java.net.http/jdk.internal.net.http.ResponseSubscribers$ByteArraySubscriber.onComplete(ResponseSubscribers.java:288)
at java.net.http/jdk.internal.net.http.ResponseSubscribers$MappingSubscriber.onComplete(ResponseSubscribers.java:695)
at java.net.http/jdk.internal.net.http.Http1Response$Http1BodySubscriber.complete(Http1Response.java:333)
at java.net.http/jdk.internal.net.http.Http1Response$Http1BodySubscriber.onComplete(Http1Response.java:383)
at java.net.http/jdk.internal.net.http.ResponseContent$ChunkedBodyParser.accept(ResponseContent.java:189)
at java.net.http/jdk.internal.net.http.ResponseContent$ChunkedBodyParser.accept(ResponseContent.java:128)
at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.handle(Http1Response.java:788)
at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.handle(Http1Response.java:718)
at java.net.http/jdk.internal.net.http.Http1Response$Receiver.accept(Http1Response.java:610)
at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.tryAsyncReceive(Http1Response.java:748)
at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:228)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Rate': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 5]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2590)
at com.fasterxml.jackson.core.JsonParser._constructReadException(JsonParser.java:2616)
at com.fasterxml.jackson.core.JsonParser._constructReadException(JsonParser.java:2624)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:830)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._reportInvalidToken(ReaderBasedJsonParser.java:3017)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleOddValue(ReaderBasedJsonParser.java:2051)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:780)
at com.fasterxml.jackson.databind.ObjectReader._initForReading(ObjectReader.java:356)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2117)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1565)
at com.github.mizosoft.methanol.adapter.jackson.JacksonAdapter$TextFormatDecoder.readValueUnchecked(JacksonAdapter.java:168)
... 21 more
Either the ResponsePayload should always be excluded from the JSON deserializers or there should be some possibility to prioritize the basic one. Currently it is also not clear from then javadoc of AdapterCodecs if multiple decoders/encoders are potential candidates.
Hi @kwin
The adapter that is selected is the first one that matches the (body-type/content-type) combo. If the response lacks a Content-Type, the substituted one is MediaType.ANY, which is matched by any adapter. So a response lacking a Content-Type will be matched with the body type only. Jackson thinks it can handle ResponsePayload.class (which is surprising), so that's why it gets selected first. Because the basic adapter is restricted in the types it can handle, and it matches with any Content-Type, it should be added first. That way, it will be always selected for basic types with any Content-Type (there doesn't seem to be a case when you'll want the Jackson adapter to match for a String or a byte[], even if the Content-Type is application/json). So AdapterCodec prioritizes encoders/decoders based on addition order.
That being said, I may consider adding a way to select/prioritize a certain encoder/decoder, tentatively through the adapter hints set on the request.