在春季启动中将protobuf作为JSON发送

时间:2018-08-27 18:02:33

标签: java spring-boot exception jackson protocol-buffers

我正在使用具有这个具体定义的protobufs。

message Hash {
    string category = 1;
    repeated KVPair content = 2;
}

message KVPair {
    string key = 1;
    string value = 2;
}

我想通过spring-boot应用程序将其作为JSON发送。 我将此程序包添加到了gradle依赖项中:

compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.6.1'

当我尝试使用以下代码输出哈希生成的对象时:

@RestController
@RequestMapping("/api/crm/")
public class KVController {

    private final KVService kvService;

    public KVController(KVService kvService) {
        this.kvService = kvService;
    }

    @GetMapping("kv/{category}")
    public Hash getHash(@PathVariable String category) {
        Hash hash = kvService.retrieve(category);
        return hash;
    }
}

它抛出了这个最终的异常:

  

原因:com.fasterxml.jackson.databind.exc.InvalidDefinitionException:直接自引用导致循环(通过引用链:com.blaazha.crm.proto.Hash [“ unknownFields”]-> com.google。 protobuf.UnknownFieldSet [“ defaultInstanceForType”])       在com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ser.BeanPropertyWriter._handleSelfReference(BeanPropertyWriter.java:944)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:721)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ObjectWriter $ Prefetch.serialize(ObjectWriter.java:1396)〜[jackson-databind-2.9.6.jar:2.9.6]       在com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:913)〜[jackson-databind-2.9.6.jar:2.9.6]       在org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:286)〜[spring-web-4.3.18.RELEASE.jar:4.3.18.RELEASE]       ...省略了58个常见框架

kvService仅从Redis返回数据。它将哈希数据类型(https://redis.io/topics/data-types)解析为原型中定义的哈希对象。其中Hash-> category是hash的主键,而hash redis数据类型中的值将转换为proto中定义的KVPair。我无法显示所有源代码,因为它会调用其他系统并且源代码很长。

kvService返回有效的Hash对象,但是当我返回此Hash对象并且spring尝试将其转换为JSON时会发生异常。

build.gradle中的重要依赖项:

def versions = [
        logback: '1.2.3',
        owner: '1.0.10',
        jackson: '2.9.6',

        guava: '25.1-jre',
        guice: '4.2.0',
        grpc: '1.9.1',
        protoc: '3.5.1',

        redis: '2.9.0',
]

依赖性{

compile group: 'ch.qos.logback', name: 'logback-classic', version: versions.logback
compile group: 'org.aeonbits.owner', name: 'owner', version: versions.owner

compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: versions.jackson
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: versions.jackson
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: versions.jackson

compile group: 'com.google.guava', name: 'guava', version: versions.guava
compile group: 'com.google.inject', name: 'guice', version: versions.guice
compile group: 'io.grpc', name: 'grpc-netty', version: versions.grpc
compile group: 'io.grpc', name: 'grpc-protobuf', version: versions.grpc
compile group: 'io.grpc', name: 'grpc-stub', version: versions.grpc
compile 'org.glassfish:javax.annotation:10.0-b28'


compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.1'
compile group: 'javax.activation', name: 'activation', version: '1.1.1'

compile group: 'redis.clients', name: 'jedis', version: versions.redis

}

您可以在我的protobuf定义中看到,它不是任何自引用。

有没有可能解决此问题的方法?

3 个答案:

答案 0 :(得分:3)

如果您使用的是 Spring WebFlux ,并尝试produces application/json,则可以采取以下措施使它适用于所有返回protobuf Message < / strong>类型:

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
    configurer.defaultCodecs().jackson2JsonEncoder(
        new Jackson2JsonEncoder(Jackson2ObjectMapperBuilder.json().serializerByType(
                Message.class, new JsonSerializer<Message>() {
                    @Override
                    public void serialize(Message value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                        String str = JsonFormat.printer().omittingInsignificantWhitespace().print(value);
                        gen.writeRawValue(str);
                    }
                }
        ).build())
    );
}

答案 1 :(得分:2)

services.Configure<ApiBehaviorOptions>(options => { options.SuppressModelStateInvalidFilter = true; }); (通过生成的方法UnknownFieldSet到达)包含getter Hash.getUnknownFields(),该方法返回getDefaultInstanceForType()的单例实例。这个单例实例在UnknownFieldSet中进行引用,而Jackson-databind无法自动处理此问题(请参见下面的edit2)。

您可能要使用getDefaultInstanceForType()中的JsonFormat,它使用canonical encoding而不是Jackson。

祝你好运!

编辑>对于春季,ProtobufJsonFormatHttpMessageConverter

EDIT2>当然,您可以使用Mix-in Annotations处理这种情况,但是IMHO JsonFormat绝对是可行的方法...

答案 2 :(得分:2)

要将protobuf对象转换为JSON,您应使用com.google.protobuf.util.JsonFormat包中的以下类:

JsonFormat.printer().print()