cosmos-sdk/orm/internal/stablejson/encode.go

94 lines
2.7 KiB
Go

package stablejson
import (
"bytes"
"fmt"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protopath"
"google.golang.org/protobuf/reflect/protorange"
"google.golang.org/protobuf/reflect/protoreflect"
)
// Marshal marshals the provided message to JSON with a stable ordering based
// on ascending field numbers.
func Marshal(message proto.Message) ([]byte, error) {
buf := &bytes.Buffer{}
firstStack := []bool{true}
err := protorange.Options{
Stable: true,
}.Range(message.ProtoReflect(),
func(p protopath.Values) error {
// Starting printing the value.
if !firstStack[len(firstStack)-1] {
_, _ = fmt.Fprintf(buf, ",")
}
firstStack[len(firstStack)-1] = false
// Print the key.
var fd protoreflect.FieldDescriptor
last := p.Index(-1)
beforeLast := p.Index(-2)
switch last.Step.Kind() {
case protopath.FieldAccessStep:
fd = last.Step.FieldDescriptor()
_, _ = fmt.Fprintf(buf, "%q:", fd.Name())
case protopath.ListIndexStep:
fd = beforeLast.Step.FieldDescriptor() // lists always appear in the context of a repeated field
case protopath.MapIndexStep:
fd = beforeLast.Step.FieldDescriptor() // maps always appear in the context of a repeated field
_, _ = fmt.Fprintf(buf, "%v:", last.Step.MapIndex().Interface())
case protopath.AnyExpandStep:
_, _ = fmt.Fprintf(buf, `"@type":%q`, last.Value.Message().Descriptor().FullName())
return nil
case protopath.UnknownAccessStep:
_, _ = fmt.Fprintf(buf, "?: ")
}
switch v := last.Value.Interface().(type) {
case protoreflect.Message:
_, _ = fmt.Fprintf(buf, "{")
firstStack = append(firstStack, true)
case protoreflect.List:
_, _ = fmt.Fprintf(buf, "[")
firstStack = append(firstStack, true)
case protoreflect.Map:
_, _ = fmt.Fprintf(buf, "{")
firstStack = append(firstStack, true)
case protoreflect.EnumNumber:
var ev protoreflect.EnumValueDescriptor
if fd != nil {
ev = fd.Enum().Values().ByNumber(v)
}
if ev != nil {
_, _ = fmt.Fprintf(buf, "%v", ev.Name())
} else {
_, _ = fmt.Fprintf(buf, "%v", v)
}
case string, []byte:
_, _ = fmt.Fprintf(buf, "%q", v)
default:
_, _ = fmt.Fprintf(buf, "%v", v)
}
return nil
},
func(p protopath.Values) error {
last := p.Index(-1)
switch last.Value.Interface().(type) {
case protoreflect.Message:
if last.Step.Kind() != protopath.AnyExpandStep {
_, _ = fmt.Fprintf(buf, "}")
}
case protoreflect.List:
_, _ = fmt.Fprintf(buf, "]")
firstStack = firstStack[:len(firstStack)-1]
case protoreflect.Map:
_, _ = fmt.Fprintf(buf, "}")
firstStack = firstStack[:len(firstStack)-1]
}
return nil
},
)
return buf.Bytes(), err
}