spring 如何在java.util.Map < String,Object>中转换google.protobuf.Struct字段?

cig3rfwq  于 2023-09-29  发布在  Spring
关注(0)|答案(4)|浏览(158)

我有一个消息定义的quotation_item.proto文件,其中有google.protobuf.Struct字段来解析元数据JSON。

message QuotationItemEntry {
    // Other fields
    google.protobuf.Struct metadata = 15;
}

此外,元数据字段在Java实体类中定义

public class QuotationItem {
    // Other entity fields
    private Map<String, Object> metadata;
}

我想使用Sping Boot 应用程序存储gRPC请求中传递的QuotationItemEntry对象,但无法转换java.util.Map<String, Object>中的google.protobuf.Struct字段。

bogh5gae

bogh5gae1#

在Java中,google.protobuf.Struct的字段值在其本机表示中不受支持。相反,它们由com.google.protobuf.Value Package 。所以用google.protobuf.Struct#getFieldsMap()方法,结果是Map<String, Value>。您需要手动填充条目值。

7cwmlq89

7cwmlq892#

voidzcy的答案正确地指出了在Java中处理Google Protobuf的Struct的复杂性,特别是使用Value Package 字段。我想提供一些更深入的理解,为那些新的Protobuf在整体上,阐明为什么这种设计的选择,以及它如何影响你作为一个开发人员。
Protobuf的Struct类型被设计成一个灵活的容器,可以容纳各种数据类型。这种设计选择对于处理半结构化或动态数据特别有益。这种灵活性允许Struct捕获不符合固定模式的数据。虽然这对于处理各种数据形状非常有用,但它带来了一个权衡:你就失去了强大的类型保证。
要更深入地理解Protobuf核心库中Struct众所周知的消息的定义,我们可以查看Github上的.proto描述
下面的代码取自google protobuf的开源代码库:

message Struct {
  // Unordered map of dynamically typed values.
  map<string, Value> fields = 1;
}

因此,从上面的代码片段中,我们可以看出结构体本质上使用了我们上面讨论过的map<>,并将Value存储到Map的字符串中。
Value是在同一个.proto文件中定义的另一条消息,下面是Value的外观:

message Value {
  oneof kind {
    NullValue null_value = 1;
    double number_value = 2;
    string string_value = 3;
    bool bool_value = 4;
    Struct struct_value = 5;
    ListValue list_value = 6;
  }
}

我们还可以查看struct.proto文件中的最后一个实体,以了解该值可以作为Value的“目录”或“列表”:

enum NullValue {
  NULL_VALUE = 0;
}

message ListValue {
  repeated Value values = 1;
}

一些语言和框架可能使用内部的protobuf结构,而没有给开发人员提供“类似JSON”的接口来与它们的Struct进行交互
例如,如果我们有以下JSON数据:

{
    "id": 1,
    "name": "foo",
    "domain": "bar",
    "isAuthenticated": true
}

我们将有内部Protobuf伪代码结构,如:

Struct {
    fields {
        key: "name"
        value {
            string_value: "foo"
        }
    }
    fields {
        key: "isAuthenticated"
        value {
            bool_value: true
        }
    }
    fields {
        key: "id"
        value {
            number_value: 1
        }
    }
    fields {
        key: "domain"
        value {
            string_value: "bar"
        }
    }
}

如果你想了解更多关于它我已经发布了一个Blog regarding this topic

fd3cxomn

fd3cxomn3#

我认为你可以通过元数据键来收集它们作为一个Map。这可以解决你的问题。

public Map<String, String> getMetadataMap(Metadata metadata) {
            Map<String, String> metadataMap = metadata.keys().stream().collect(
                Collectors.toMap(
                    key -> key,
                    key -> metadata.get(Metadata.Key.of(key,
                                        Metadata.ASCII_STRING_MARSHALLER))));
            return metadataMap;
          }
mm9b1k5b

mm9b1k5b4#

Kotlin的解决方案:

fun Struct.toMap(): Map<String, Any?> =
    this.fieldsMap.mapValues { it.value.toAny() }

fun Value.toAny(): Any? =
    when (kindCase) {
        NULL_VALUE -> null
        NUMBER_VALUE -> numberValue
        STRING_VALUE -> stringValue
        BOOL_VALUE -> boolValue
        STRUCT_VALUE -> structValue.toMap()
        LIST_VALUE -> listValue.valuesList.map { it.toAny() }
        else -> error("Incorrect protobuf value $this")
    }

使用方法:

val map: Map<String, Any?> = youProtoStruct.toMap()

相关问题