@@ -36,6 +36,8 @@ final class ProtobufDataEncoderContext implements ObjectEncoderContext {
36
36
private final Map <Class <?>, ObjectEncoder <?>> objectEncoders ;
37
37
private final Map <Class <?>, ValueEncoder <?>> valueEncoders ;
38
38
private final ObjectEncoder <Object > fallbackEncoder ;
39
+ private final ProtobufValueEncoderContext valueEncoderContext =
40
+ new ProtobufValueEncoderContext (this );
39
41
40
42
private static final FieldDescriptor MAP_KEY_DESC =
41
43
FieldDescriptor .builder ("key" ).withProperty (AtProtobuf .builder ().tag (1 ).build ()).build ();
@@ -95,12 +97,18 @@ public ObjectEncoderContext add(@NonNull String name, boolean value) throws IOEx
95
97
@ Override
96
98
public ObjectEncoderContext add (@ NonNull FieldDescriptor field , @ Nullable Object obj )
97
99
throws IOException {
100
+ return add (field , obj , true );
101
+ }
102
+
103
+ ObjectEncoderContext add (
104
+ @ NonNull FieldDescriptor field , @ Nullable Object obj , boolean skipDefault )
105
+ throws IOException {
98
106
if (obj == null ) {
99
107
return this ;
100
108
}
101
109
if (obj instanceof CharSequence ) {
102
110
CharSequence seq = (CharSequence ) obj ;
103
- if (seq .length () == 0 ) {
111
+ if (skipDefault && seq .length () == 0 ) {
104
112
return this ;
105
113
}
106
114
int tag = getTag (field );
@@ -115,7 +123,10 @@ public ObjectEncoderContext add(@NonNull FieldDescriptor field, @Nullable Object
115
123
@ SuppressWarnings ("unchecked" )
116
124
Collection <Object > collection = (Collection <Object >) obj ;
117
125
for (Object value : collection ) {
118
- add (field , value );
126
+ // It's important not to skip "default" values in repeated fields as there is a difference
127
+ // between having an empty list and a list of "default" items,
128
+ // e.g. encoding ["", ""] should not result in an empty list when encoded.
129
+ add (field , value , false );
119
130
}
120
131
return this ;
121
132
}
@@ -124,30 +135,33 @@ public ObjectEncoderContext add(@NonNull FieldDescriptor field, @Nullable Object
124
135
@ SuppressWarnings ("unchecked" )
125
136
Map <Object , Object > map = (Map <Object , Object >) obj ;
126
137
for (Map .Entry <Object , Object > entry : map .entrySet ()) {
127
- doEncode (DEFAULT_MAP_ENCODER , field , entry );
138
+ // It's important not to skip "default" values in map fields as there is a difference
139
+ // between having an empty map and a map of 2 "default" items,
140
+ // e.g. encoding {"": 0] should not result in an empty map when encoded.
141
+ doEncode (DEFAULT_MAP_ENCODER , field , entry , false );
128
142
}
129
143
return this ;
130
144
}
131
145
132
146
if (obj instanceof Double ) {
133
- return add (field , (double ) obj );
147
+ return add (field , (double ) obj , skipDefault );
134
148
}
135
149
136
150
if (obj instanceof Float ) {
137
- return add (field , (float ) obj );
151
+ return add (field , (float ) obj , skipDefault );
138
152
}
139
153
140
154
if (obj instanceof Number ) {
141
- return add (field , ((Number ) obj ).longValue ());
155
+ return add (field , ((Number ) obj ).longValue (), skipDefault );
142
156
}
143
157
144
158
if (obj instanceof Boolean ) {
145
- return add (field , (boolean ) obj );
159
+ return add (field , (boolean ) obj , skipDefault );
146
160
}
147
161
148
162
if (obj instanceof byte []) {
149
163
byte [] bytes = (byte []) obj ;
150
- if (bytes .length == 0 ) {
164
+ if (skipDefault && bytes .length == 0 ) {
151
165
return this ;
152
166
}
153
167
int tag = getTag (field );
@@ -163,12 +177,12 @@ public ObjectEncoderContext add(@NonNull FieldDescriptor field, @Nullable Object
163
177
(ObjectEncoder <Object >) objectEncoders .get (obj .getClass ());
164
178
165
179
if (objectEncoder != null ) {
166
- return doEncode (objectEncoder , field , obj );
180
+ return doEncode (objectEncoder , field , obj , skipDefault );
167
181
}
168
182
@ SuppressWarnings ("unchecked" )
169
183
ValueEncoder <Object > valueEncoder = (ValueEncoder <Object >) valueEncoders .get (obj .getClass ());
170
184
if (valueEncoder != null ) {
171
- return doEncode (valueEncoder , field , obj );
185
+ return doEncode (valueEncoder , field , obj , skipDefault );
172
186
}
173
187
174
188
if (obj instanceof ProtoEnum ) {
@@ -177,13 +191,18 @@ public ObjectEncoderContext add(@NonNull FieldDescriptor field, @Nullable Object
177
191
if (obj instanceof Enum ) {
178
192
return add (field , ((Enum <?>) obj ).ordinal ());
179
193
}
180
- return doEncode (fallbackEncoder , field , obj );
194
+ return doEncode (fallbackEncoder , field , obj , skipDefault );
181
195
}
182
196
183
197
@ NonNull
184
198
@ Override
185
199
public ObjectEncoderContext add (@ NonNull FieldDescriptor field , double value ) throws IOException {
186
- if (value == 0 ) {
200
+ return add (field , value , true );
201
+ }
202
+
203
+ ObjectEncoderContext add (@ NonNull FieldDescriptor field , double value , boolean skipDefault )
204
+ throws IOException {
205
+ if (skipDefault && value == 0 ) {
187
206
return this ;
188
207
}
189
208
int tag = getTag (field );
@@ -196,7 +215,13 @@ public ObjectEncoderContext add(@NonNull FieldDescriptor field, double value) th
196
215
@ NonNull
197
216
@ Override
198
217
public ObjectEncoderContext add (@ NonNull FieldDescriptor field , float value ) throws IOException {
199
- if (value == 0 ) {
218
+
219
+ return add (field , value , true );
220
+ }
221
+
222
+ ObjectEncoderContext add (@ NonNull FieldDescriptor field , float value , boolean skipDefault )
223
+ throws IOException {
224
+ if (skipDefault && value == 0 ) {
200
225
return this ;
201
226
}
202
227
int tag = getTag (field );
@@ -210,7 +235,12 @@ public ObjectEncoderContext add(@NonNull FieldDescriptor field, float value) thr
210
235
@ Override
211
236
public ProtobufDataEncoderContext add (@ NonNull FieldDescriptor field , int value )
212
237
throws IOException {
213
- if (value == 0 ) {
238
+ return add (field , value , true );
239
+ }
240
+
241
+ ProtobufDataEncoderContext add (@ NonNull FieldDescriptor field , int value , boolean skipDefault )
242
+ throws IOException {
243
+ if (skipDefault && value == 0 ) {
214
244
return this ;
215
245
}
216
246
Protobuf protobuf = getProtobuf (field );
@@ -235,7 +265,12 @@ public ProtobufDataEncoderContext add(@NonNull FieldDescriptor field, int value)
235
265
@ Override
236
266
public ProtobufDataEncoderContext add (@ NonNull FieldDescriptor field , long value )
237
267
throws IOException {
238
- if (value == 0 ) {
268
+ return add (field , value , true );
269
+ }
270
+
271
+ ProtobufDataEncoderContext add (@ NonNull FieldDescriptor field , long value , boolean skipDefault )
272
+ throws IOException {
273
+ if (skipDefault && value == 0 ) {
239
274
return this ;
240
275
}
241
276
Protobuf protobuf = getProtobuf (field );
@@ -260,10 +295,12 @@ public ProtobufDataEncoderContext add(@NonNull FieldDescriptor field, long value
260
295
@ Override
261
296
public ProtobufDataEncoderContext add (@ NonNull FieldDescriptor field , boolean value )
262
297
throws IOException {
263
- if (!value ) {
264
- return this ;
265
- }
266
- return add (field , 1 );
298
+ return add (field , value , true );
299
+ }
300
+
301
+ ProtobufDataEncoderContext add (@ NonNull FieldDescriptor field , boolean value , boolean skipDefault )
302
+ throws IOException {
303
+ return add (field , value ? 1 : 0 , skipDefault );
267
304
}
268
305
269
306
@ NonNull
@@ -299,10 +336,11 @@ public ObjectEncoderContext nested(@NonNull FieldDescriptor field) throws IOExce
299
336
}
300
337
301
338
private <T > ProtobufDataEncoderContext doEncode (
302
- ObjectEncoder <T > encoder , FieldDescriptor field , T obj ) throws IOException {
339
+ ObjectEncoder <T > encoder , FieldDescriptor field , T obj , boolean skipDefault )
340
+ throws IOException {
303
341
304
342
long size = determineSize (encoder , obj );
305
- if (size == 0 ) {
343
+ if (skipDefault && size == 0 ) {
306
344
return this ;
307
345
}
308
346
@@ -330,9 +368,10 @@ private <T> long determineSize(ObjectEncoder<T> encoder, T obj) throws IOExcepti
330
368
}
331
369
332
370
private <T > ProtobufDataEncoderContext doEncode (
333
- ValueEncoder <T > encoder , FieldDescriptor field , T obj ) throws IOException {
334
- // TODO(vkryachko): consider reusing value encoder contexts to avoid allocations.
335
- encoder .encode (obj , new ProtobufValueEncoderContext (field , this ));
371
+ ValueEncoder <T > encoder , FieldDescriptor field , T obj , boolean skipDefault )
372
+ throws IOException {
373
+ valueEncoderContext .resetContext (field , skipDefault );
374
+ encoder .encode (obj , valueEncoderContext );
336
375
return this ;
337
376
}
338
377
0 commit comments