Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion common/types/pb/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ import (
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
)

var (
// UseJSONFieldNames enables protobuf JSON field names for field access instead of the short name of the declaration.
//
// This should be set before any types are loaded into the Db.
//
// For example, given a proto field declaration:
//
// message Sample {
// int32 sample_field = 1;
// }
//
// The default field name for access is "sample_field". If UseJSONFieldNames is set to true,
// the field name for access becomes "sampleField".
UseJSONFieldNames = false
)

// description is a private interface used to make it convenient to perform type unwrapping at
// the TypeDescription or FieldDescription level.
type description interface {
Expand All @@ -47,7 +63,11 @@ func newTypeDescription(typeName string, desc protoreflect.MessageDescriptor, ex
fields := desc.Fields()
for i := 0; i < fields.Len(); i++ {
f := fields.Get(i)
fieldMap[string(f.Name())] = newFieldDescription(f)
if UseJSONFieldNames {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you move this flag up as a function argument, you should be able to pass the same argument into the pb.Db instance which generates the internal type descriptions.

Copy link
Collaborator

@TristonianJones TristonianJones Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, you'll want to update the common/types/pb.go definition for the ``Db` struct:

type fieldNameKind int

const (
   fieldNameKind proto = iota
   json
   jsonOrProto
)

type Db struct {
	revFileDescriptorMap map[string]*FileDescription
	// files contains the deduped set of FileDescriptions whose types are contained in the pb.Db.
	files []*FileDescription
	// extensions contains the mapping between a given type name, extension name and its FieldDescription
	extensions map[string]map[string]*FieldDescription
	
	// fieldNameConvention supports type resolution using either the protobuf name or its JSON name
    fieldNameConvention fieldNameKind
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would then need to support a functional option to the NewDb call:

// NewDb creates a new `pb.Db` with an empty type name to file description map.
func NewDb(DbOption ...opts) *Db {
	pbdb := &Db{ ... }
	for _, opt := range opts {
	   pbdb = opt(pbdb)
	}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me look into that, thanks for the response.

fieldMap[string(f.JSONName())] = newFieldDescription(f)
} else {
fieldMap[string(f.Name())] = newFieldDescription(f)
}
}
return &TypeDescription{
typeName: typeName,
Expand Down
69 changes: 69 additions & 0 deletions common/types/pb/type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,75 @@ func TestFieldDescriptionGetFrom(t *testing.T) {
}
}

func TestFieldDescriptionGetFromJSON(t *testing.T) {
UseJSONFieldNames = true
defer func() { UseJSONFieldNames = false }()

pbdb := NewDb()
msg := &proto3pb.TestAllTypes{
SingleUint64: 12,
SingleDuration: dpb.New(time.Duration(1234)),
SingleTimestamp: tpb.New(time.Unix(12345, 0).UTC()),
SingleBoolWrapper: wrapperspb.Bool(false),
SingleInt32Wrapper: wrapperspb.Int32(42),
StandaloneEnum: proto3pb.TestAllTypes_BAR,
NestedType: &proto3pb.TestAllTypes_SingleNestedMessage{
SingleNestedMessage: &proto3pb.TestAllTypes_NestedMessage{
Bb: 123,
},
},
SingleValue: structpb.NewStringValue("hello world"),
SingleStruct: jsonStruct(t, map[string]any{
"null": nil,
}),
}
msgName := string(msg.ProtoReflect().Descriptor().FullName())
_, err := pbdb.RegisterMessage(msg)
if err != nil {
t.Fatalf("pbdb.RegisterMessage(%q) failed: %v", msgName, err)
}
td, found := pbdb.DescribeType(msgName)
if !found {
t.Fatalf("pbdb.DescribeType(%q) not found", msgName)
}
expected := map[string]any{
"singleUint64": uint64(12),
"singleDuration": time.Duration(1234),
"singleTimestamp": time.Unix(12345, 0).UTC(),
"singleBoolWrapper": false,
"singleInt32Wrapper": int32(42),
"singleInt64Wrapper": structpb.NullValue_NULL_VALUE,
"singleNestedMessage": &proto3pb.TestAllTypes_NestedMessage{
Bb: 123,
},
"standaloneEnum": int64(1),
"singleValue": "hello world",
"singleStruct": jsonStruct(t, map[string]any{
"null": nil,
}),
}
for field, want := range expected {
f, found := td.FieldByName(field)
if !found {
t.Fatalf("td.FieldByName(%q) not found", field)
}
got, err := f.GetFrom(msg)
if err != nil {
t.Fatalf("field.GetFrom() failed: %v", err)
}
switch g := got.(type) {
case proto.Message:
if !proto.Equal(g, want.(proto.Message)) {
t.Errorf("got field %s value %v, wanted %v", field, g, want)
}
default:
if !reflect.DeepEqual(g, want) {
t.Errorf("got field %s value %v, wanted %v", field, g, want)
}
}
}
}

func TestFieldDescriptionIsSet(t *testing.T) {
pbdb := NewDb()
msg := &proto3pb.TestAllTypes{}
Expand Down