Skip to content

Commit 40825a1

Browse files
Add option to enable strict JSON parsing in Protobuf Java JsonFormat.
JsonFormat used 'reader.setLenient(false)' to try to get spec JSON behavior out of GSON. However, this mode was still not actually spec JSON. In ~2024 GSON deprecated setLenient(), made that behavior LEGACY_STRICT and added a new level STRICT which is intended to be spec. Switching unilaterally would be a breaking change for Protobuf: this change just adds the option for users to ask us to use GSON in strict mode. At a later date we will consider flipping the default of this (letting people opt into LEGACY_STRICT if they want to). PiperOrigin-RevId: 921432331
1 parent 4376cba commit 40825a1

1 file changed

Lines changed: 50 additions & 10 deletions

File tree

java/util/src/main/java/com/google/protobuf/util/JsonFormat.java

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,8 @@ public static Parser parser() {
489489
TypeRegistry.getEmptyTypeRegistry(),
490490
ExtensionRegistry.getEmptyRegistry(),
491491
false,
492-
Parser.DEFAULT_RECURSION_LIMIT);
492+
Parser.DEFAULT_RECURSION_LIMIT,
493+
true);
493494
}
494495

495496
/** A Parser parses the ProtoJSON format into a protobuf message. */
@@ -499,6 +500,7 @@ public static class Parser {
499500
private final ExtensionRegistry extensionRegistry;
500501
private final boolean ignoringUnknownFields;
501502
private final int recursionLimit;
503+
private final boolean legacyLenient;
502504

503505
// The default parsing recursion limit is aligned with the proto binary parser.
504506
private static final int DEFAULT_RECURSION_LIMIT = 100;
@@ -508,12 +510,14 @@ private Parser(
508510
TypeRegistry oldRegistry,
509511
ExtensionRegistry extensionRegistry,
510512
boolean ignoreUnknownFields,
511-
int recursionLimit) {
513+
int recursionLimit,
514+
boolean legacyLenient) {
512515
this.registry = registry;
513516
this.oldRegistry = oldRegistry;
514517
this.extensionRegistry = extensionRegistry;
515518
this.ignoringUnknownFields = ignoreUnknownFields;
516519
this.recursionLimit = recursionLimit;
520+
this.legacyLenient = legacyLenient;
517521
}
518522

519523
/**
@@ -532,7 +536,8 @@ public Parser usingTypeRegistry(TypeRegistry oldRegistry) {
532536
oldRegistry,
533537
extensionRegistry,
534538
ignoringUnknownFields,
535-
recursionLimit);
539+
recursionLimit,
540+
legacyLenient);
536541
}
537542

538543
/**
@@ -547,7 +552,12 @@ public Parser usingTypeRegistry(com.google.protobuf.TypeRegistry registry) {
547552
throw new IllegalArgumentException("Only one registry is allowed.");
548553
}
549554
return new Parser(
550-
registry, oldRegistry, extensionRegistry, ignoringUnknownFields, recursionLimit);
555+
registry,
556+
oldRegistry,
557+
extensionRegistry,
558+
ignoringUnknownFields,
559+
recursionLimit,
560+
legacyLenient);
551561
}
552562

553563
/**
@@ -559,15 +569,21 @@ Parser usingExtensionRegistry(ExtensionRegistry extensionRegistry) {
559569
throw new NullPointerException();
560570
}
561571
return new Parser(
562-
registry, oldRegistry, extensionRegistry, ignoringUnknownFields, recursionLimit);
572+
registry,
573+
oldRegistry,
574+
extensionRegistry,
575+
ignoringUnknownFields,
576+
recursionLimit,
577+
legacyLenient);
563578
}
564579

565580
/**
566581
* Creates a new {@link Parser} configured to not throw an exception when an unknown field is
567582
* encountered. The new Parser clones all other configurations from this Parser.
568583
*/
569584
public Parser ignoringUnknownFields() {
570-
return new Parser(this.registry, oldRegistry, extensionRegistry, true, recursionLimit);
585+
return new Parser(
586+
this.registry, oldRegistry, extensionRegistry, true, recursionLimit, legacyLenient);
571587
}
572588

573589
/**
@@ -580,7 +596,12 @@ public void merge(String json, Message.Builder builder) throws InvalidProtocolBu
580596
// TODO: Investigate the allocation overhead and optimize for
581597
// mobile.
582598
new ParserImpl(
583-
registry, oldRegistry, extensionRegistry, ignoringUnknownFields, recursionLimit)
599+
registry,
600+
oldRegistry,
601+
extensionRegistry,
602+
ignoringUnknownFields,
603+
recursionLimit,
604+
legacyLenient)
584605
.merge(json, builder);
585606
}
586607

@@ -595,14 +616,24 @@ public void merge(Reader json, Message.Builder builder) throws IOException {
595616
// TODO: Investigate the allocation overhead and optimize for
596617
// mobile.
597618
new ParserImpl(
598-
registry, oldRegistry, extensionRegistry, ignoringUnknownFields, recursionLimit)
619+
registry,
620+
oldRegistry,
621+
extensionRegistry,
622+
ignoringUnknownFields,
623+
recursionLimit,
624+
legacyLenient)
599625
.merge(json, builder);
600626
}
601627

602628
// For testing only.
603629
Parser usingRecursionLimit(int recursionLimit) {
604630
return new Parser(
605-
registry, oldRegistry, extensionRegistry, ignoringUnknownFields, recursionLimit);
631+
registry,
632+
oldRegistry,
633+
extensionRegistry,
634+
ignoringUnknownFields,
635+
recursionLimit,
636+
legacyLenient);
606637
}
607638
}
608639

@@ -1411,25 +1442,31 @@ private static class ParserImpl {
14111442
private final ExtensionRegistry extensionRegistry;
14121443
private final boolean ignoringUnknownFields;
14131444
private final int recursionLimit;
1445+
private final boolean legacyLenient;
14141446
private int currentDepth;
14151447

14161448
ParserImpl(
14171449
com.google.protobuf.TypeRegistry registry,
14181450
TypeRegistry oldRegistry,
14191451
ExtensionRegistry extensionRegistry,
14201452
boolean ignoreUnknownFields,
1421-
int recursionLimit) {
1453+
int recursionLimit,
1454+
boolean legacyLenient) {
14221455
this.registry = registry;
14231456
this.oldRegistry = oldRegistry;
14241457
this.extensionRegistry = extensionRegistry;
14251458
this.ignoringUnknownFields = ignoreUnknownFields;
14261459
this.recursionLimit = recursionLimit;
1460+
this.legacyLenient = legacyLenient;
14271461
this.currentDepth = 0;
14281462
}
14291463

14301464
void merge(Reader json, Message.Builder builder) throws IOException {
14311465
try {
14321466
JsonReader reader = new JsonReader(json);
1467+
if (!legacyLenient) {
1468+
throw new IllegalStateException("Unreachable: !legacyLenient should not be set.");
1469+
}
14331470
reader.setLenient(false);
14341471
merge(JsonParser.parseReader(reader), builder);
14351472
} catch (JsonIOException e) {
@@ -1448,6 +1485,9 @@ void merge(Reader json, Message.Builder builder) throws IOException {
14481485
void merge(String json, Message.Builder builder) throws InvalidProtocolBufferException {
14491486
try {
14501487
JsonReader reader = new JsonReader(new StringReader(json));
1488+
if (!legacyLenient) {
1489+
throw new IllegalStateException("Strict parsing is not supported in open-source.");
1490+
}
14511491
reader.setLenient(false);
14521492
merge(JsonParser.parseReader(reader), builder);
14531493
} catch (RuntimeException e) {

0 commit comments

Comments
 (0)