Skip to content

Commit 276b2ff

Browse files
committed
Merge branch '3.2' into 3.x
2 parents 5bcac8b + 166f4d5 commit 276b2ff

10 files changed

Lines changed: 340 additions & 29 deletions

File tree

protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/FieldType.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,20 @@ private FieldType(int wt, DataType.ScalarType... aliases) {
5959

6060
public int getWireType() { return _wireType; }
6161

62+
/**
63+
* Whether fields of this type may use "packed" encoding when repeated
64+
* (per protobuf spec, only scalar numeric/enum/boolean types can be packed;
65+
* length-delimited types like String/Bytes/Message cannot).
66+
*/
67+
public boolean isPackable() {
68+
return _wireType != WireType.LENGTH_PREFIXED;
69+
}
70+
6271
public boolean usesZigZag() {
6372
return (this == VINT32_Z) || (this == VINT64_Z);
6473
}
6574

66-
public Iterable< DataType.ScalarType> aliases() {
75+
public Iterable<DataType.ScalarType> aliases() {
6776
return Arrays.asList(_aliases);
6877
}
6978
}

protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/FileDescriptorSet.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,14 @@ public static class FileDescriptorProto
136136

137137
public ProtoFile.Syntax getSyntax()
138138
{
139-
if (syntax == null) {
140-
return ProtoFile.Syntax.PROTO_2;
139+
// 01-Jul-2026, tatu: [dataformats-binary#134] Real `FileDescriptorProto.syntax`
140+
// values (as written by protoc) are lowercase ("proto2"/"proto3"), but the
141+
// enum constants are PROTO_2/PROTO_3: `valueOf(syntax)` would throw for any
142+
// proto3-origin descriptor set.
143+
if ("proto3".equals(syntax)) {
144+
return ProtoFile.Syntax.PROTO_3;
141145
}
142-
return ProtoFile.Syntax.valueOf(syntax);
146+
return ProtoFile.Syntax.PROTO_2;
143147
}
144148

145149
public void setPackage(String p) { _package = p; }

protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/NativeProtobufSchema.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,27 @@ public class NativeProtobufSchema
1515
protected final String _name;
1616
protected final Collection<TypeElement> _nativeTypes;
1717

18+
/**
19+
* @since 2.21.5 [dataformats-binary#134]
20+
*/
21+
protected final boolean _isProto3;
22+
1823
protected volatile String[] _messageNames;
1924

2025
protected NativeProtobufSchema(ProtoFile input)
2126
{
22-
this(input.filePath(), input.typeElements());
27+
this(input.filePath(), input.typeElements(), input.syntax() == ProtoFile.Syntax.PROTO_3);
28+
}
29+
30+
protected NativeProtobufSchema(String name, Collection<TypeElement> types) {
31+
this(name, types, false);
2332
}
2433

25-
protected NativeProtobufSchema(String name, Collection<TypeElement> types)
34+
protected NativeProtobufSchema(String name, Collection<TypeElement> types, boolean isProto3)
2635
{
2736
_name = name;
2837
_nativeTypes = types;
38+
_isProto3 = isProto3;
2939
}
3040

3141
public static NativeProtobufSchema construct(ProtoFile input) {
@@ -64,7 +74,7 @@ public ProtobufSchema forType(String messageTypeName)
6474
+"') has no message type with name '"+messageTypeName+"': known types: "
6575
+getMessageNames());
6676
}
67-
return new ProtobufSchema(this, TypeResolver.resolve(_nativeTypes, msg));
77+
return new ProtobufSchema(this, TypeResolver.resolve(_nativeTypes, msg, _isProto3));
6878
}
6979

7080
/**
@@ -78,7 +88,7 @@ public ProtobufSchema forFirstType()
7888
throw new IllegalArgumentException("Protobuf schema definition (name '"+_name
7989
+"') contains no message type definitions");
8090
}
81-
return new ProtobufSchema(this, TypeResolver.resolve(_nativeTypes, msg));
91+
return new ProtobufSchema(this, TypeResolver.resolve(_nativeTypes, msg, _isProto3));
8292
}
8393

8494
public List<String> getMessageNames() {

protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/ProtobufField.java

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,36 @@ public class ProtobufField
6262
public final boolean isStdEnum;
6363

6464
public ProtobufField(FieldElement nativeField, FieldType type) {
65-
this(nativeField, type, null, null);
65+
this(nativeField, type, false);
66+
}
67+
68+
/**
69+
* @param isProto3 Whether enclosing schema uses proto3 syntax: affects default
70+
* "packed" setting for repeated scalar fields when not explicitly specified
71+
* (proto3 defaults to packed, proto2 to unpacked)
72+
*/
73+
public ProtobufField(FieldElement nativeField, FieldType type, boolean isProto3) {
74+
this(nativeField, type, null, null, isProto3);
6675
}
6776

6877
public ProtobufField(FieldElement nativeField, ProtobufMessage msg) {
69-
this(nativeField, FieldType.MESSAGE, msg, null);
78+
this(nativeField, FieldType.MESSAGE, msg, null, false);
7079
}
7180

7281
public ProtobufField(FieldElement nativeField, ProtobufEnum et) {
73-
this(nativeField, FieldType.ENUM, null, et);
82+
this(nativeField, FieldType.ENUM, null, et, false);
83+
}
84+
85+
public ProtobufField(FieldElement nativeField, ProtobufEnum et, boolean isProto3) {
86+
this(nativeField, FieldType.ENUM, null, et, isProto3);
7487
}
7588

7689
public static ProtobufField unknownField() {
77-
return new ProtobufField(null, FieldType.MESSAGE, null, null);
90+
return new ProtobufField(null, FieldType.MESSAGE, null, null, false);
7891
}
7992

8093
protected ProtobufField(FieldElement nativeField, FieldType type,
81-
ProtobufMessage msg, ProtobufEnum et)
94+
ProtobufMessage msg, ProtobufEnum et, boolean isProto3)
8295
{
8396
this.type = type;
8497
wireType = type.getWireType();
@@ -118,8 +131,15 @@ protected ProtobufField(FieldElement nativeField, FieldType type,
118131
* we can't use 'isPacked()' in 3.1.5 (and probably deprecated has same issue);
119132
* let's add a temporary workaround.
120133
*/
121-
packed = _findBooleanOption(nativeField, "packed");
122-
deprecated = _findBooleanOption(nativeField, "deprecated");
134+
Boolean explicitPacked = _findBooleanOptionValue(nativeField, "packed");
135+
if (explicitPacked != null) {
136+
packed = explicitPacked.booleanValue();
137+
} else {
138+
// 01-Jul-2026: [dataformats-binary#134] proto3 defaults repeated
139+
// scalar/enum fields to packed encoding unless overridden
140+
packed = repeated && isProto3 && type.isPackable();
141+
}
142+
deprecated = Boolean.TRUE.equals(_findBooleanOptionValue(nativeField, "deprecated"));
123143

124144
// 13-Apr-2017, tatu: [databind#79] Need to write length-prefixed for packed arrays
125145
if (repeated && packed) {
@@ -132,18 +152,18 @@ protected ProtobufField(FieldElement nativeField, FieldType type,
132152
isObject = (type == FieldType.MESSAGE);
133153
}
134154

135-
private static boolean _findBooleanOption(FieldElement f, String key)
155+
private static Boolean _findBooleanOptionValue(FieldElement f, String key)
136156
{
137157
for (OptionElement opt : f.options()) {
138158
if (key.equals(opt.name())) {
139159
Object val = opt.value();
140160
if (val instanceof Boolean) {
141-
return ((Boolean) val).booleanValue();
161+
return (Boolean) val;
142162
}
143-
return "true".equals(String.valueOf(val).trim());
163+
return Boolean.valueOf("true".equals(String.valueOf(val).trim()));
144164
}
145165
}
146-
return false;
166+
return null;
147167
}
148168

149169
public void assignMessageType(ProtobufMessage msgType) {

protobuf/src/main/java/tools/jackson/dataformat/protobuf/schema/TypeResolver.java

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,22 @@ public class TypeResolver
4040
*/
4141
private Map<String,ProtobufMessage> _resolvedMessageTypes;
4242

43-
protected TypeResolver(TypeResolver p, String name, Map<String,MessageElement> declaredMsgs,
44-
Map<String,ProtobufEnum> enums)
43+
/**
44+
* Whether enclosing schema (single .proto file) uses proto3 syntax: affects
45+
* default "packed" setting for repeated scalar/enum fields.
46+
*
47+
* @since 2.21.5 [dataformats-binary#134]
48+
*/
49+
private final boolean _isProto3;
50+
51+
protected TypeResolver(TypeResolver p, String name,
52+
Map<String,MessageElement> declaredMsgs,
53+
Map<String,ProtobufEnum> enums, boolean isProto3)
4554
{
4655
_parent = p;
4756
_contextName = name;
4857
_enumTypes = enums;
58+
_isProto3 = isProto3;
4959
if (declaredMsgs == null) {
5060
declaredMsgs = Collections.emptyMap();
5161
}
@@ -56,23 +66,34 @@ protected TypeResolver(TypeResolver p, String name, Map<String,MessageElement> d
5666
/**
5767
* Main entry method for public API, for resolving specific root-level type and other
5868
* types it depends on.
69+
*
70+
* @deprecated Since 3.2
5971
*/
72+
@Deprecated
6073
public static ProtobufMessage resolve(Collection<TypeElement> nativeTypes, MessageElement rawType) {
61-
final TypeResolver rootR = construct(null, null, nativeTypes);
74+
return resolve(nativeTypes, rawType, false);
75+
}
76+
77+
/**
78+
* @since 3.1.5 [dataformats-binary#134]
79+
*/
80+
public static ProtobufMessage resolve(Collection<TypeElement> nativeTypes, MessageElement rawType,
81+
boolean isProto3) {
82+
final TypeResolver rootR = construct(null, null, nativeTypes, isProto3);
6283
// Important: parent context for "root types", but child context for nested; further,
6384
// resolution happens in "child" context to allow proper referencing
64-
return TypeResolver.construct(rootR, rawType.name(), rawType.nestedElements())
85+
return TypeResolver.construct(rootR, rawType.name(), rawType.nestedElements(), isProto3)
6586
._resolve(rawType);
6687
}
6788

6889
protected ProtobufMessage resolve(TypeResolver parent, MessageElement rawType)
6990
{
70-
return TypeResolver.construct(this, rawType.name(), rawType.nestedElements())
91+
return TypeResolver.construct(this, rawType.name(), rawType.nestedElements(), _isProto3)
7192
._resolve(rawType);
7293
}
7394

7495
protected static TypeResolver construct(TypeResolver parent, String localName,
75-
Collection<TypeElement> nativeTypes)
96+
Collection<TypeElement> nativeTypes, boolean isProto3)
7697
{
7798
Map<String,MessageElement> declaredMsgs = null;
7899
Map<String,ProtobufEnum> declaredEnums = new LinkedHashMap<>();
@@ -92,7 +113,7 @@ protected static TypeResolver construct(TypeResolver parent, String localName,
92113
}
93114
} // no other known types?
94115
}
95-
return new TypeResolver(parent, localName, declaredMsgs, declaredEnums);
116+
return new TypeResolver(parent, localName, declaredMsgs, declaredEnums, isProto3);
96117
}
97118

98119
protected void addEnumType(String name, ProtobufEnum enumType) {
@@ -125,6 +146,17 @@ protected static ProtobufEnum constructEnum(EnumElement nativeEnum)
125146
protected ProtobufMessage _resolve(MessageElement rawType)
126147
{
127148
List<FieldElement> rawFields = rawType.fields();
149+
List<OneOfElement> oneOfs = rawType.oneOfs();
150+
// 01-Jul-2026, tatu: [dataformats-binary#134] Fields declared inside a
151+
// `oneof` block live in a separate list from regular fields and were
152+
// silently dropped during resolution; merge them in so they're not lost.
153+
if (!oneOfs.isEmpty()) {
154+
List<FieldElement> merged = new ArrayList<FieldElement>(rawFields);
155+
for (OneOfElement oneOf : oneOfs) {
156+
merged.addAll(oneOf.fields());
157+
}
158+
rawFields = merged;
159+
}
128160
ProtobufField[] resolvedFields = new ProtobufField[rawFields.size()];
129161

130162
ProtobufMessage message = new ProtobufMessage(rawType.name(), resolvedFields);
@@ -142,7 +174,7 @@ protected ProtobufMessage _resolve(MessageElement rawType)
142174
ProtobufField pbf;
143175

144176
if (type != null) { // simple type
145-
pbf = new ProtobufField(f, type);
177+
pbf = new ProtobufField(f, type, _isProto3);
146178
} else if (fieldType instanceof DataType.NamedType) {
147179
final String typeStr = ((DataType.NamedType) fieldType).name();
148180

@@ -246,7 +278,7 @@ private ProtobufField _findDottedType(FieldElement nativeField, String typeStr)
246278
}
247279
// Create a resolver in the context of the outer type and recursively
248280
// resolve the remaining path (handles arbitrary nesting depth)
249-
TypeResolver outerResolver = TypeResolver.construct(this, outerName, outerMsg.nestedElements());
281+
TypeResolver outerResolver = TypeResolver.construct(this, outerName, outerMsg.nestedElements(), _isProto3);
250282
return outerResolver._findAndResolve(nativeField, innerPath);
251283
}
252284

@@ -284,7 +316,7 @@ private ProtobufField _findLocalResolved(FieldElement nativeField, String typeSt
284316
}
285317
ProtobufEnum et = _enumTypes.get(typeStr);
286318
if (et != null) {
287-
return new ProtobufField(nativeField, et);
319+
return new ProtobufField(nativeField, et, _isProto3);
288320
}
289321
return null;
290322
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package tools.jackson.dataformat.protobuf;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import tools.jackson.databind.JsonNode;
6+
import tools.jackson.databind.node.ObjectNode;
7+
8+
import tools.jackson.dataformat.protobuf.schema.ProtobufSchema;
9+
import tools.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader;
10+
11+
import static org.junit.jupiter.api.Assertions.assertEquals;
12+
import static org.junit.jupiter.api.Assertions.assertNotNull;
13+
14+
// [dataformats-binary#134]: fields declared inside a `oneof` block live in a
15+
// separate list (`MessageElement.oneOfs()`) from regular fields
16+
// (`MessageElement.fields()`); TypeResolver only ever resolved the latter,
17+
// so `oneof` member fields were silently dropped from the schema -- no
18+
// error, they simply couldn't be read or written.
19+
public class OneofFieldResolutionTest extends ProtobufTestBase
20+
{
21+
private final ProtobufMapper MAPPER = newObjectMapper();
22+
23+
private final static String SCHEMA_STR = "message t {\n"
24+
+ " oneof choice {\n"
25+
+ " string a = 1;\n"
26+
+ " int32 b = 2;\n"
27+
+ " }\n"
28+
+ " optional string other = 3;\n"
29+
+ "}\n";
30+
31+
@Test
32+
public void testOneofFieldsAreResolved() throws Exception
33+
{
34+
ProtobufSchema schema = ProtobufSchemaLoader.std.parse(SCHEMA_STR);
35+
36+
assertEquals(3, schema.getRootType().getFieldCount());
37+
assertNotNull(schema.getRootType().field("a"));
38+
assertNotNull(schema.getRootType().field("b"));
39+
assertNotNull(schema.getRootType().field("other"));
40+
}
41+
42+
@Test
43+
public void testOneofFieldsRoundTrip() throws Exception
44+
{
45+
ProtobufSchema schema = ProtobufSchemaLoader.std.parse(SCHEMA_STR);
46+
47+
ObjectNode input = MAPPER.createObjectNode();
48+
input.put("a", "value-for-a");
49+
input.put("other", "other-value");
50+
51+
byte[] bytes = MAPPER.writer(schema).writeValueAsBytes(input);
52+
JsonNode result = MAPPER.readerFor(JsonNode.class).with(schema).readValue(bytes);
53+
54+
assertEquals("value-for-a", result.get("a").asString());
55+
assertEquals("other-value", result.get("other").asString());
56+
}
57+
}

0 commit comments

Comments
 (0)