docs/design/editions/edition-zero-json-handling.md
Author: @mkruskal-google
Approved: 2023-05-10
Today, proto3 fully validates JSON mappings for uniqueness during parsing, while proto2 takes a best-effort approach and allows cases that don't have a 1:1 mapping. This is laid out in more detail by JSON Field Name Conflicts (not available externally). While we had hoped to unify these before Protobuf editions launched, we ended up blocked by some internal use-cases. This issue is now blocking the editions launch, since we can't represent this behavior with the current set of Edition Zero features.
Today, by default, we transform each field name to a CamelCase name that will
always be valid, but not necessarily unique in JSON. We also support a
json_name field option to override this for JSON parsing/serialization. This
allows conflicts to potentially arise where many proto fields map to the same
JSON field. Our JSON handling has the following behaviors:
deprecated_legacy_json_field_conflicts optionjson_name set
deprecated_legacy_json_field_conflicts isn't setThe goal here is to unify these behaviors into a future-facing feature as part of edition zero.
We recommend adding a new json_format feature as part of
Edition Zero features. The doc will be updated to
reflect the following details.
JSON format can have three possible states:
ALLOW - By default, fields will be fully validated during proto parsing.
Any conflicting JSON mappings will trigger protoc errors, guaranteeing
uniqueness. This will be consistent with the current proto3 behavior. No
runtime changes are needed, since we allow JSON parsing/serialization.DISALLOW - Alternatively, we will ban JSON encoding and disable all
validation related to JSON mappings. All runtimes will fail to parse or
serialize any messages to/from JSON when this feature is set on the
top-level messages. This is a new mode which provides an alternative to
LEGACY_BEST_EFFORT that doesn't involve any schema changes.LEGACY_BEST_EFFORT - Fields will be validated for correctness, but not for
uniqueness. Any conflicting JSON mappings will trigger protoc warnings, but
no errors. This will be consistent with the current proto2 behavior, or
proto3 where deprecated_legacy_json_field_conflicts is set. Since this is
undefined behavior we want to get rid of, a parallel effort will attempt to
remove this later. No runtime changes are needed, since we allow JSON
parsing/serialization.Long-term, we want JSON support to be specified at the proto level. For the
migration from proto2/proto3, we will just migrate everything to ALLOW and
LEGACY_BEST_EFFORT depending on the syntax and the value of
deprecated_legacy_json_field_conflicts.
We will additionally ban any ALLOW message from containing a DISALLOW type
anywhere in its tree (including extensions, which will fail to compile).
Attempting to add this will result in a compiler error. This has the following
benefits:
The implementation is a lot simpler, since most of the work is done in protoc and parsers only need to check the top level message
Runtime failures aren't dependent on the contents of the message being serialized/parsed
Avoids messy blurring of ownership. If a bug occurs because a DISALLOW
field is sometimes set, is the owner of the child type required to change it
to ALLOW? Or is the owner of the parent type responsible because they
added the dependency?
LEGACY_BEST_EFFORT will continue to allow serialization/parsing of types
with DISALLOW set.
This feature will target messages and enums, but we will also provide it at the file level for convenience.
Example use-cases for DISALLOW:
Instead of a tri-state feature, we could have a simple allow/disallow feature for JSON format.
DISALLOW, others are actually
depending on our current behavior under JSON mapping conflicts (as a
hack around some limitations in JSON customization).Instead of defaulting to ALLOW, we could default to DISALLOW.
The majority of internal Google protos are used for binary/text encoding and don't care about JSON, so this would:
DISALLOW and may have
fields with conflicting JSON mappingsDISALLOW can be added