为什么唱片类不能适当地与Jackson?

tnkciper  于 12个月前  发布在  其他
关注(0)|答案(3)|浏览(139)

我正在为我的电报频道开发简单的机器人,我使用Java 16的功能,如记录类。问题是我不能将传入的请求转换为记录类。我使用Jackson和Adjacent aut来设置客户端。下面是我的类:

用户记录

public record User(
        long id,
        boolean isBot,
        String firstName,
        String userName,
        boolean canJoinGroups,
        boolean canReadAllGroupMessages,
        boolean supportsInlineQueries) {
}

字符串

TelegramResponse记录

public record TelegramResponse<T>(T result, boolean ok) {
}

Telegram ApiClient类

@Client("https://api.telegram.org/bot${bot.id}")
public interface TelegramApiClient {

    @Get("/getMe")
    TelegramResponse<User> getSelf();
}


当我调用这个方法时,我得到这个错误:

16:22:52.916 [default-nioEventLoopGroup-1-2] ERROR i.m.h.s.netty.RoutingInBoundHandler - Unexpected error occurred: Error decoding HTTP response body: Error decoding stream for type [class com.praytic.TelegramResponse]: Can not set final java.lang.Boolean field com.praytic.User.supportsInlineQueries to java.lang.Boolean (through reference chain: com.praytic.TelegramResponse["result"])
io.micronaut.http.client.exceptions.HttpClientResponseException: Error decoding HTTP response body: Error decoding stream for type [class com.praytic.TelegramResponse]: Can not set final java.lang.Boolean field com.praytic.User.supportsInlineQueries to java.lang.Boolean (through reference chain: com.praytic.TelegramResponse["result"])
    at io.micronaut.http.client.netty.DefaultHttpClient$11.channelReadInstrumented(DefaultHttpClient.java:2191)
    at io.micronaut.http.client.netty.DefaultHttpClient$11.channelReadInstrumented(DefaultHttpClient.java:2061)
    at io.micronaut.http.client.netty.DefaultHttpClient$SimpleChannelInboundHandlerInstrumented.channelRead0(DefaultHttpClient.java:2765)
    at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.micronaut.http.netty.stream.HttpStreamsHandler.channelRead(HttpStreamsHandler.java:194)
    at io.micronaut.http.netty.stream.HttpStreamsClientHandler.channelRead(HttpStreamsClientHandler.java:183)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
    at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1368)
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1234)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1280)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:507)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:446)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: io.micronaut.http.codec.CodecException: Error decoding stream for type [class com.praytic.TelegramResponse]: Can not set final java.lang.Boolean field com.praytic.User.supportsInlineQueries to java.lang.Boolean (through reference chain: com.praytic.TelegramResponse["result"])
    at io.micronaut.jackson.codec.JacksonMediaTypeCodec.decode(JacksonMediaTypeCodec.java:209)
    at io.micronaut.http.client.netty.FullNettyClientHttpResponse.convertByteBuf(FullNettyClientHttpResponse.java:280)
    at io.micronaut.http.client.netty.FullNettyClientHttpResponse.lambda$getBody$1(FullNettyClientHttpResponse.java:218)
    at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1224)
    at io.micronaut.http.client.netty.FullNettyClientHttpResponse.getBody(FullNettyClientHttpResponse.java:192)
    at io.micronaut.http.client.netty.FullNettyClientHttpResponse.<init>(FullNettyClientHttpResponse.java:111)
    at io.micronaut.http.client.netty.DefaultHttpClient$11.channelReadInstrumented(DefaultHttpClient.java:2121)
    ... 52 common frames omitted
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not set final java.lang.Boolean field com.praytic.User.supportsInlineQueries to java.lang.Boolean (through reference chain: com.praytic.TelegramResponse["result"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:274)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:623)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:611)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:634)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.set(FieldProperty.java:193)
    at com.fasterxml.jackson.databind.deser.impl.PropertyValue$Regular.assign(PropertyValue.java:62)
    at com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator.build(PropertyBasedCreator.java:211)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:520)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:362)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:195)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:565)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:449)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:362)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:195)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3643)
    at io.micronaut.jackson.codec.JacksonMediaTypeCodec.decode(JacksonMediaTypeCodec.java:204)
    ... 58 common frames omitted
Caused by: java.lang.IllegalAccessException: Can not set final java.lang.Boolean field com.praytic.User.supportsInlineQueries to java.lang.Boolean
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
    at java.base/jdk.internal.reflect.UnsafeQualifiedObjectFieldAccessorImpl.set(UnsafeQualifiedObjectFieldAccessorImpl.java:79)
    at java.base/java.lang.reflect.Field.set(Field.java:793)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.set(FieldProperty.java:190)
    ... 74 common frames omitted


我从this的文章中读到Jackson2.12已经支持java.lang.Record类型。我的Jackson配置为java.lang.Record

jackson:
  property-naming-strategy: SNAKE_CASE
  serialization-inclusion: always

编辑:

正如我猜测的那样,在记录头中的每个字段上放置@JsonProperty注解是有效的。异常消失了,所有的值都是流行的。

public record User(
        long id,
        @JsonProperty("is_bot") boolean isBot,
        @JsonProperty("first_name") String firstName,
        @JsonProperty("user_name") String userName,
        @JsonProperty("can_join_groups") boolean canJoinGroups,
        @JsonProperty("can_read_all_group_messages") boolean canReadAllGroupMessages,
        @JsonProperty("supports_inline_queries") boolean supportsInlineQueries) {
}


但是,我不想对每个DTO记录都这样做。如何使jackson.property-naming-strategy: SNAKE_CASE属性工作?

7tofc5zh

7tofc5zh1#

有两种解决方案:
1.等到Jackson修复这个问题,截至2.13.0-rc 02 this issue仍然没有解决。
1.使用GitHub用户在上述问题的评论中提出的解决方案。

public class RecordNamingStrategyPatchModule extends SimpleModule {

    @Override
    public void setupModule(SetupContext context) {
        context.addValueInstantiators(new ValueInstantiatorsModifier());
        super.setupModule(context);
    }

    /**
     * Remove when the following issue is resolved: 
     * <a href="https://github.com/FasterXML/jackson-databind/issues/2992">Properties naming strategy do not work with Record #2992</a>
     */
    private static class ValueInstantiatorsModifier extends ValueInstantiators.Base {
        @Override
        public ValueInstantiator findValueInstantiator(
                DeserializationConfig config, BeanDescription beanDesc, ValueInstantiator defaultInstantiator
        ) {
            if (!beanDesc.getBeanClass().isRecord() || !(defaultInstantiator instanceof StdValueInstantiator) || !defaultInstantiator.canCreateFromObjectWith()) {
                return defaultInstantiator;
            }
            Map<String, BeanPropertyDefinition> map = beanDesc.findProperties().stream().collect(Collectors.toMap(p -> p.getInternalName(), Function.identity()));
            SettableBeanProperty[] renamedConstructorArgs = Arrays.stream(defaultInstantiator.getFromObjectArguments(config))
                    .map(p -> {
                        BeanPropertyDefinition prop = map.get(p.getName());
                        return prop != null ? p.withName(prop.getFullName()) : p;
                    })
                    .toArray(SettableBeanProperty[]::new);

            return new PatchedValueInstantiator((StdValueInstantiator) defaultInstantiator, renamedConstructorArgs);
        }
    }

    private static class PatchedValueInstantiator extends StdValueInstantiator {

        protected PatchedValueInstantiator(StdValueInstantiator src, SettableBeanProperty[] constructorArguments) {
            super(src);
            _constructorArguments = constructorArguments;
        }
    }
}

字符串
可以使用以下命令将模块添加到ObjectMapper

objectMapper.registerModule(new RecordNamingStrategyPatchModule());

jchrr9hc

jchrr9hc2#

如果我理解正确的话,问题不在于序列化,而在于非序列化。对于非序列化,Jackson需要读取构造函数中每个参数的名称,以便将字段正确地从JSONMap到Java。
自动完成此操作的一种方法(无需@JsonProperty注解)是从jackson-module-parameter-names jar添加参数化模块:

mapper.registerModule(new ParameterNamesModule());

字符串
这也要求类使用**-parameters**compile标志进行编译(至少对于java 8 - 11)。在maven中,您可以通过以下方式进行编译:

<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <compilerArgs>
            <arg>-parameters</arg>
          </compilerArgs>
        </configuration>
      </plugin>
    </plugins>
  </build>


我从来没有用Java 16测试过这个,所以让我知道它是否也能在那里工作。

gijlo24d

gijlo24d3#

我的解决方案(Jackson2.13.x)是向记录类添加一个规范构造函数并使用@JsonCreator

record Thing(
    String one,
    String two,
    Boolean someFlag
) {
    public Thing(String one, String two) {
        this(one, two, null);
    }

    @JsonCreator
    public Thing(
        String one,
        String two,
        @JsonProperty("some_flag") Boolean someFlag
    ) {
        this.one = one;
        this.two = two;
        this.someFlag = Objects.requireNonNullElse(someFlag, true);
    }
}

字符串

相关问题