【问题标题】:Handling null values in protobuffers处理 protobuffers 中的空值
【发布时间】:2014-02-09 06:20:20
【问题描述】:

我正在研究从数据库中获取数据并构造 protobuff 消息的东西。鉴于可以从数据库中为某些字段获取空值的可能性,我将在尝试构造 protobuff 消息时得到空指针异常。从线程 http://code.google.com/p/protobuf/issues/detail?id=57 得知 protobuffs 中不支持 null,我想知道处理抛出 NPE 的唯一其他方法是否是将手动检查插入到与 proto 对应的 java 文件中,如下所示!

message ProtoPerson{
    optional string firstName = 1;
    optional string lastName = 2;
    optional string address1 = 3;
}

ProtoPerson.Builder builder = ProtoPerson.Builder.newBuilder();
if (p.getFirstName() != null) builder.setFirstName(p.getFirstName());
if (p.getLastName() != null) builder.setLastName(p.getLastName());
if (p.getAddress1() != null) builder.setAddress1(p.getAddress1());
...

那么有人可以澄清一下在 protobuff 构建期间是否有任何其他可能的有效方法来处理 null 值?

【问题讨论】:

标签: java null protocol-buffers


【解决方案1】:

免责声明:Google 员工每天都会使用 protobufs 回答。我绝不代表 Google。

  1. 将您的原型命名为 Person 而不是 PersonProto 或 ProtoPerson。编译的 protobuf 只是您使用的语言指定的类定义,并进行了一些改进。添加“Proto”是多余的。
  2. 使用YourMessage.hasYourField() 而不是YourMessage.getYourField() != null。 protobuf 字符串的默认值是一个空字符串,它 NOT 等于 null。然而,无论您的字段是未设置、清除还是空字符串,.hasYourField() 始终返回 false。见default values for common protobuf field types。
  3. 您可能已经知道,但我想明确地说:不要以编程方式将 protobuf 字段设置为 null。 即使在 protobuf 之外,nullcauses all sorts of problems。请改用.clearYourField()。
  4. Person.Builder 类确实 NOT 有 .newBuilder() 方法。 Person 类可以。像这样理解 Builder 模式:只有当你还没有新的 builder 时,你才能创建它。

重写你的 protobuf:

message Person {
  optional string first_name = 1;
  optional string last_name = 2;
  optional string address_1 = 3;
}

重写你的逻辑:

Person thatPerson = Person.newBuilder()
    .setFirstName("Aaa")
    .setLastName("Bbb")
    .setAddress1("Ccc")
    .build();

Person.Builder thisPersonBuilder = Person.newBuilder()

if (thatPerson.hasFirstName()) {
  thisPersonBuilder.setFirstName(thatPerson.getFirstName());
}

if (thatPerson.hasLastName()) {
  thisPersonBuilder.setLastName(thatPerson.getLastName());
}

if (thatPerson.hasAddress1()) {
  thisPersonBuilder.setAddress1(thatPerson.getAddress1());
}

Person thisPerson = thisPersonBuilder.build();

如果thatPerson 是您创建的人员对象,其属性值可以是空字符串、空格或空值,那么我建议使用Guava's Strings library:

import static com.google.common.base.Strings.nullToEmpty;

Person.Builder thisPersonBuilder = Person.newBuilder()

if (!nullToEmpty(thatPerson.getFirstName()).trim().isEmpty()) {
  thisPersonBuilder.setFirstName(thatPerson.getFirstName());
}

if (!nullToEmpty(thatPerson.hasLastName()).trim().isEmpty()) {
  thisPersonBuilder.setLastName(thatPerson.getLastName());
}

if (!nullToEmpty(thatPerson.hasAddress1()).trim().isEmpty()) {
  thisPersonBuilder.setAddress1(thatPerson.getAddress1());
}

Person thisPerson = thisPersonBuilder.build();

【讨论】:

  • 您的帖子可能充满了很好的建议……但我不确定您是否理解他的问题?例如,他的数据不是来自另一个 protobuf 对象;它来自数据库或其他来源,并且可能有空值。基本上,他想知道如何轻松处理空值。
  • 好点 Erhannis,谢谢。我通过添加一个部分来处理具有空字符串值、连续空间值和空值的非协议缓冲区对象来更新答案。
  • '导致各种问题'链接失效,我真的很想了解更多
  • @kinbiko:修复了链接。 Guava 已从 Google Code 迁移到 GitHub,因此现在有了新链接。谢谢你告诉我!
  • 这个答案与问题中的建议不一样吗 - 只需使用空检查?相关部分 - 回答问题的部分仅在最后一段中。令人困惑的是,答案的另一部分是回答尚未提出的问题。
【解决方案2】:

原型 3

wrappers.proto 支持可空值:

  • 字符串(字符串值),
  • int(Int32Value),
  • bool(BoolValue)
  • 等等

例子

syntax = "proto3";
import "google/protobuf/wrappers.proto";

message ProtoPerson {
    google.protobuf.StringValue firstName = 1;
    google.protobuf.StringValue lastName = 2;
    google.protobuf.StringValue address1 = 3;
    google.protobuf.Int32Value age = 4;
}

【讨论】:

【解决方案3】:

对此没有简单的解决方案。我建议只处理空检查。但如果你真的想摆脱它们,这里有几个想法:

  • 您可以编写一个code generator plugin,将setOrClearFoo() 方法添加到每个Java 类。 Java 代码生成器为此提供了insertion points(参见该页末尾)。
  • 您可以使用 Java 反射遍历 p 的 get*() 方法,调用每个方法,检查 null,然后调用 builder 的 set*() 方法(如果非空)。这将具有额外的优势,即您不必在每次添加新字段时都更新复制代码,但它比编写显式复制每个字段的代码要慢得多。

【讨论】:

  • 有没有人真正创建了一个代码生成器插件来执行 setOrClearFoo() 或提供一个示例?
  • 我尝试过反射,但随着原型变大,性能急剧下降,您需要将大型数据集映射到原型对象。
猜你喜欢
  • 2018-09-23
  • 2017-06-05
  • 2012-05-23
  • 1970-01-01
  • 1970-01-01
  • 2015-10-27
  • 2018-08-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多