protoyaml

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 6 Imported by: 1

README

protoyaml

protoyaml is a goccy/go-yaml-based canonical protojson⇔YAML bridge. It is unrelated to bufbuild/protoyaml-go, which is built on a different YAML engine and pursues different design goals.

Definition

protobuf's JSON representation is defined by protojson. This module renders and parses that representation in YAML syntax.

By default the output is the canonical protojson mapping. protojson-sanctioned variants are opt-in through WithProtoJSON: passing a protojson.MarshalOptions value enables the knobs protojson itself defines — UseProtoNames, UseEnumNumbers, EmitUnpopulated, a custom type Resolver, and so on. There is no non-protojson (reflection-based) mode: every value this module emits or accepts is a protojson representation written in YAML rather than JSON syntax.

Why anchor on protojson? The protojson mapping already answers every representation question protobuf has (enum names, int64 as string, well-known type encodings such as Timestamp/Duration/Struct, and so on). Rendering that mapping in YAML syntax keeps the behavior predictable and the semantics identical to protojson; a second, YAML-specific mapping would only introduce ambiguity. So protoyaml treats protojson as the semantics anchor and uses goccy/go-yaml purely for syntax.

API

func Marshal(m proto.Message, opts ...Option) ([]byte, error)
func Unmarshal(b []byte, m proto.Message) error      // YAML -> JSON -> protojson.Unmarshal (DiscardUnknown)
func UnmarshalJSON(j []byte, m proto.Message) error  // protojson.Unmarshal (DiscardUnknown)
func YAMLToJSON(y []byte) ([]byte, error)             // goccy YAML -> interface{} -> encoding/json

func WithYAMLOptions(opts ...yaml.EncodeOption) Option // style pass-through
func WithFlowLeafCollections() Option                 // see below
func WithProtoJSON(o protojson.MarshalOptions) Option // default: zero value
Marshal pipeline
  1. protojson.Marshal(m) produces canonical JSON (the semantics anchor).
  2. That JSON is decoded with goccy/go-yaml using UseOrderedMap. Because JSON is valid YAML flow syntax, this yields an ordered value tree that preserves protojson's key order (field-number order, map keys sorted by protojson).
  3. The ordered value is rendered as YAML, optionally reshaped by WithFlowLeafCollections.
Example
out, _ := protoyaml.Marshal(planNode, protoyaml.WithFlowLeafCollections())
fmt.Print(string(out))
index: 1
kind: RELATIONAL
displayName: Unit Relation
childLinks:
- {childIndex: 2}
metadata: {execution_method: Row}
executionStats:
  cpu_time: {total: "0", unit: msecs}
  execution_summary: {num_executions: "1"}
  latency: {total: "0", unit: msecs}
  rows: {total: "1", unit: rows}
WithFlowLeafCollections

By default every collection renders in block style. WithFlowLeafCollections renders every mapping whose values are all scalars in flow style ({k: v, k2: v2}) instead. The rules are:

  • Leaf mappings (all values are scalars) become flow style.
  • Non-leaf mappings (containing a nested mapping or sequence) stay block style.
  • Sequences always stay block style.

This keeps the outer structure readable while compacting the innermost records. It is implemented by building goccy's encoder AST and flipping the flow-style flag on qualifying MappingNodes before serialization, so the result is real YAML produced by goccy, not string surgery.

Compatibility

The exact output bytes are part of the compatibility surface: a change to the rendered bytes is a breaking change, subject to this module's versioning policy. The semantics are inherited from protojson, so protobuf's JSON mapping rules apply unchanged; changes in the protobuf library's protojson output propagate here.

Known limitations

These stem from goccy/go-yaml's scalar handling and are pinned by characterization tests (TestDoubleLexicalEdgeLimitations, TestAnyUnmarshalResolverGap) so any behavior change is caught:

  • Negative zero: protojson emits -0 for a negative-zero double, but the YAML bridge decodes it as integer 0, so the sign is lost on Marshal and the value round-trips to +0.
  • Exponent-form doubles without a decimal point (e.g. 1e+21, 5e-324): the emitted YAML scalar is unquoted and reads back as a string in the generic JSON tree (YAMLToJSON). Unmarshal into a proto message still works because protojson accepts string-encoded numbers for double fields.
  • Any with a custom type resolver: WithProtoJSON supplies a resolver to Marshal only; Unmarshal/UnmarshalJSON use the global type registry. An Any whose type is known only to a custom resolver marshals but does not unmarshal. An unmarshal-side option may be added later.

License

MIT. See LICENSE.

Documentation

Overview

Package protoyaml renders and parses protobuf messages as YAML using the canonical protojson mapping.

Definition

protobuf's JSON representation is defined by protojson. This package renders and parses that representation in YAML syntax, using goccy/go-yaml as the YAML engine.

By default the output is the canonical protojson mapping. protojson-sanctioned variants are opt-in through WithProtoJSON: passing a protojson.MarshalOptions value enables the knobs protojson itself defines (UseProtoNames, UseEnumNumbers, EmitUnpopulated, a custom type Resolver, and so on). There is no non-protojson (reflection-based) mode: every value this package emits or accepts is a protojson representation written in YAML rather than JSON syntax.

This is unrelated to github.com/bufbuild/protoyaml-go, which uses a different YAML engine and pursues different design goals.

Marshal pipeline

Marshal drives protojson for semantics and goccy/go-yaml for syntax:

  1. protojson.Marshal(m) produces canonical JSON.
  2. The JSON is decoded with goccy/go-yaml using UseOrderedMap. Because JSON is valid YAML flow syntax, this yields an ordered value tree that preserves protojson's key order (field-number order, map keys sorted by protojson).
  3. The ordered value is rendered as YAML, optionally with flow-style leaf collections (see WithFlowLeafCollections).

Unmarshal pipeline

Unmarshal converts YAML to JSON with YAMLToJSON and then decodes with protojson using DiscardUnknown, so unknown keys are ignored rather than rejected. UnmarshalJSON exposes the JSON-side decode directly.

Compatibility

The exact output bytes are part of the compatibility surface: a change to the rendered bytes is a breaking change. The semantics are inherited from protojson, so protobuf's JSON mapping rules (enum names, int64 as string, well-known type encodings, and so on) apply unchanged.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Marshal

func Marshal(m proto.Message, opts ...Option) ([]byte, error)

Marshal renders m as YAML using the protojson mapping. By default that is the canonical protojson mapping; WithProtoJSON opts into the protojson-sanctioned variants (UseProtoNames, UseEnumNumbers, EmitUnpopulated, a custom Resolver).

The pipeline is: protojson.Marshal(m) produces the JSON, that JSON is parsed by goccy/go-yaml with UseOrderedMap so protojson's key order is preserved (JSON is valid YAML flow syntax), and the ordered value is rendered back out as YAML. There is intentionally no non-protojson (reflection) path.

func Unmarshal

func Unmarshal(b []byte, m proto.Message) error

Unmarshal parses YAML into m using the canonical protojson mapping.

It converts YAML to JSON (see YAMLToJSON) and then decodes with protojson using DiscardUnknown, so unknown fields are ignored rather than rejected.

func UnmarshalJSON

func UnmarshalJSON(j []byte, m proto.Message) error

UnmarshalJSON decodes canonical protojson bytes into m with DiscardUnknown.

func YAMLToJSON

func YAMLToJSON(y []byte) ([]byte, error)

YAMLToJSON converts YAML bytes into JSON bytes suitable for protojson.Unmarshal. It decodes the YAML into a generic value with goccy/go-yaml (which normalizes mapping keys to strings) and re-encodes it with encoding/json. This mirrors the reference implementation in github.com/apstndb/spannerplan.

Types

type Option

type Option func(*config)

Option configures Marshal. Options are applied in order.

func WithFlowLeafCollections

func WithFlowLeafCollections() Option

WithFlowLeafCollections renders every mapping whose values are all scalars in YAML flow style (for example {k: v, k2: v2}) instead of block style. Sequences always stay in block style, non-leaf mappings (those that contain a nested mapping or sequence) always stay in block style, and the document root mapping always stays in block style so the output reads as a YAML document even when the whole message is scalar-only. The result is more compact for deeply nested leaf records while keeping the outer structure readable.

func WithProtoJSON

func WithProtoJSON(o protojson.MarshalOptions) Option

WithProtoJSON sets the protojson.MarshalOptions used for the protojson stage of Marshal. The zero value (the canonical protojson mapping) is used by default; passing a non-zero value opts into the protojson-sanctioned variants protojson itself defines (EmitUnpopulated, UseProtoNames, UseEnumNumbers, a custom type Resolver, and so on). Marshal always drives protojson for its semantics; this option only selects which protojson marshal options apply.

This configures the marshal side only. The unmarshal side uses a fixed protojson.UnmarshalOptions{DiscardUnknown: true} with the default (global) type resolver; there is currently no option to supply a custom resolver to Unmarshal (see UnmarshalJSON).

func WithYAMLOptions

func WithYAMLOptions(opts ...yaml.EncodeOption) Option

WithYAMLOptions passes goccy/go-yaml encode options through to the YAML rendering stage. Use it for stylistic control such as yaml.Indent, yaml.IndentSequence, or yaml.UseLiteralStyleIfMultiline. These options only affect the lexical shape of the output; they never change its semantics.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL