Skip to content

Commit 7bd3f9e

Browse files
Document unfortunate non-roundtrip behavior in ProtoJSON documentation (especially FieldMask)
PiperOrigin-RevId: 919695117
1 parent 7dd2b99 commit 7bd3f9e

1 file changed

Lines changed: 49 additions & 1 deletion

File tree

content/programming-guides/json.md

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ neutral representation.
214214
<td>FieldMask</td>
215215
<td>string</td>
216216
<td><code>"f.fooBar,h"</code></td>
217-
<td>See <code>field_mask.proto</code>.</td>
217+
<td>See <code>field_mask.proto</code>. Note that some field names cannot be represented in JSON FieldMasks, see <a href="#fieldmask-limitations">FieldMask Round-trip Limitations</a>.</td>
218218
</tr>
219219
<tr>
220220
<td>ListValue</td>
@@ -573,6 +573,54 @@ number of seconds the two representations may match (like `10s`), but the
573573
ProtoJSON durations accept fractional values and conformant implementations must
574574
precisely represent nanosecond precision (like `10.500000001s`).
575575

576+
## Non-Round Trippable Edge cases of Well-Known Types {#wkt-roundtrip-limitations}
577+
578+
While ProtoJSON defines canonical JSON representations for all Protocol Buffer
579+
types, under some edge cases Well-Known Types are not supported to round-trip
580+
cleanly (i.e., deserializing JSON back into proto, or vice versa) due to design
581+
bugs in the format.
582+
583+
For example, `google.protobuf.Value` has a double field for JSON numbers. JSON
584+
numbers can represent any double except for `Infinity`, `-Infinity` and `NaN`;
585+
constructing `Value` message with those three values set will result in a
586+
message that cannot be round-tripped through the JSON format.
587+
588+
These edge cases are rare. When they occur, implementations may fail to
589+
serialize, or they may serialize something that does not parse back to exactly
590+
the same as the original value.
591+
592+
### Non-Round Trippable FieldMasks {#fieldmask-limitations}"
593+
594+
One of the most significant round-trip limitations is that
595+
`google.protobuf.FieldMask` in JSON representation cannot be used to represent
596+
field names that are not losslessly convertible between `snake_case` and
597+
`camelCase`.
598+
599+
When representing a `FieldMask` in JSON, the original field names are converted
600+
from `snake_case` to `lowerCamelCase`. When parsing the JSON `FieldMask` back,
601+
the parser has to convert the camelCase path segments back to snake_case.
602+
603+
However, round-tripping to/from camelCase is not lossless. For example:
604+
605+
* **Field names containing underscores followed by numbers** (e.g., `x_0`,
606+
`custom_label_0`): The camelCase representation of `x_0` is `x0`. When
607+
converting `x0` back to snake_case, it remains `x0`, failing to restore the
608+
underscore.
609+
* **Field names with consecutive underscores or leading/trailing underscores**
610+
(e.g., `_x`, `__foo_bar`, `foo__bar`): The capitalization and restoration
611+
rules cannot differentiate these from standard camelCase.
612+
613+
When processing standard fields, ambiguities can be resolved by looking at the
614+
field definitions in the specific schema to look for matches. But because
615+
`FieldMask` paths are not associated with any specific message type during
616+
parsing, the conversion steps cannot refer to the schema. This means that some
617+
field names are not round-trip representable.
618+
619+
To avoid issues, follow the standard Protobuf style guide and avoid using
620+
underscores followed by numbers or consecutive/leading/trailing underscores.
621+
Starting in Edition 2024, these naming patterns are rejected by `protoc` to
622+
prevent issues.
623+
576624
## JSON Options {#json-options}
577625

578626
A conformant protobuf JSON implementation may provide the following options:

0 commit comments

Comments
 (0)