Wire versus Protoc¶
Non-Primitive Types¶
Protoc generates literal equivalents for all the Proto3 new types like empty
, struct
, etc. Wire
tries to reuse existing types in the corresponding language when possible. The only new type Wire
brings is AnyMessage
for the google.protobuf.Any
proto type.
Any¶
The Any
type wraps an arbitrary protobuf message by holding a field to identify its type and
another field for storing the serialized representation of the wrapped message. Wire comes with its
own AnyMessage
type to represent google.protobuf.Any
.
class AnyMessage(
val typeUrl: String,
val value: okio.ByteString
)
It comes with a few methods to wrap or unwrap the embedded message.
// Wire
val anyMessage: AnyMessage = AnyMessage.pack(person)
val person: Person = anyMessage.unpack(Person.ADAPTER)
// Protoc
val any: Any = Any.pack(foo)
val person: Person = any.unpack(Person.class)
Duration & Timestamp¶
Both google.protobuf.Duration
and google.protobuf.Timestamp
types will be generated by using
their JVM equivalent: java.time.Duration
and java.time.Instant
. For non-JVM platforms, we
provide two new Wire types with the same APIs:
class com.squareup.wire.Duration {
fun getSeconds(): Long
fun getNano(): Int
}
fun durationOfSeconds(seconds: Long, nano: Long): Duration
class com.squareup.wire.Instant {
fun getEpochSecond(): Long
fun getNano(): Int
}
fun ofEpochSecond(epochSecond: Long, nano: Long): Instant
// Wire
val duration: java.time.Duration = Duration.standardMinutes(15)
val instant: java.time.Instant = Instant.now()
// Protoc
val duration: google.protobuf.Duration =
Duration.newBuilder()
.setSeconds(60 * 15)
.build()
val instant: google.protobuf.Timestamp =
Timestamps.fromMillis(System.currentTimeMillis())
Struct¶
google.protobuf.Struct
is meant mainly to represent JSON objects in code. Instead of building new
types, Wire reuses Java/Kotlin native types to represent all Struct types.
Google Protobuf Type | Wire’s Java Equivalent | Wire’s Kotlin Equivalent |
---|---|---|
Struct |
Map<String, ?> |
Map<String, ?>? |
ListValue |
List<?> |
List<?>? |
Value |
Object |
Any? |
NullValue |
Void |
Nothing? |
One difference worth noting between Protoc and Wire is that Protoc can make the difference between
an absent value, and a null
value, Wire doesn’t. Wire will always write null
s in JSON objects
except at the root of it.
// Wire
val struct = mapOf("a" to 1.0)
val list = listOf(“b”, 2.0)
val boolValue = true
val nullValue = null
// Protoc
val struct: Struct =
Struct.newBuilder().apply {
putFields(“a”, Value.newBuilder.setNumberValue(1.0).build())
}
.build()
val list: List =
ListValue.newBuilder().apply {
addValues(Value.newBuilder.setStringValue(“a”).build())
addValues(Value.newBuilder.setNumberValue(2.0).build())
}
.build()
val boolValue = Value.newBuilder.setBoolValue(true).build()
val nullValue = Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()
Wrappers¶
Wire didn’t create new types for wrappers either, each wrapper will be represented by a nullable
version of the primitive type it defines. For instance google.protobuf.FloatValue
will be
represented in Java by the float boxed type @Nullable Float
, in Kotlin by Float?
.
// Wire
val floatValue = 33.3f
// Protoc
val floatValue = FloatValue.newBuilder().setValue(33.3f).build()
JSON¶
While Proto2 didn’t, Proto3 defines Protobuf serialization over JSON. Wire and Protoc are interoperable but their API are quite different. Wire offers JSON serialization over Moshi or Gson. Protoc brings its own JsonFormatter. Beware that Protoc throws an error for unknown fields, you need to configure it to opt-out of this behavior!
// Wire & Moshi
val moshi = Moshi.Builder()
.add(WireJsonAdapterFactory())
.build()
val adapter = moshi.adapter(Pizza::class.java)
val pizza: Pizza = ...
val json = adapter.toJson(pizza)
val parsedPizza = adapter.fromJson(json)
// Protoc
val pizza: PizzaOuterClass.Pizza = …
val json = JsonFormat.printer().print(value)
val jsonParser = JsonFormat.parser().ignoringUnknownFields()
val parsedBuilder = PizzaOuterClass.Pizza.newBuilder()
jsonParser.merge(json, parsedBuilder)
val parsedPizza = parsedBuilder.build()