Skip to content

Commit 06b4908

Browse files
committed
test: implemented ProtoJSON decoder mapper
1 parent 24d515b commit 06b4908

File tree

8 files changed

+359
-197
lines changed

8 files changed

+359
-197
lines changed

decoder/protojson/decoder.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,50 @@ import (
1111
type (
1212
Decoder struct {
1313
unmarshalOptions protojson.UnmarshalOptions
14+
cache bool
15+
cachedMappers map[string]*Mapper
16+
}
17+
18+
// Options are the additional settings for the decoder implementation
19+
Options struct {
20+
// cache indicates whether to cache the precompute unmarshal by reflection functions
21+
cache bool
1422
}
1523
)
1624

25+
// NewOptions creates a new Options instance
26+
//
27+
// Parameters:
28+
//
29+
// - cache: indicates whether to cache the precompute unmarshal by reflection functions
30+
//
31+
// Returns:
32+
//
33+
// - *Options: the new Options instance
34+
func NewOptions(
35+
cache bool,
36+
) *Options {
37+
return &Options{
38+
cache: cache,
39+
}
40+
}
41+
1742
// NewDecoder creates a new Decoder instance
43+
//
44+
// Parameters:
45+
//
46+
// - options: the additional settings for the decoder implementation
1847
//
1948
// Returns:
2049
//
2150
// - *Decoder: The decoder instance
22-
func NewDecoder() *Decoder {
51+
func NewDecoder(options *Options) *Decoder {
52+
// Initialize cache setting
53+
cache := false
54+
if options != nil {
55+
cache = options.cache
56+
}
57+
2358
// Initialize unmarshal options
2459
unmarshalOptions := protojson.UnmarshalOptions{
2560
DiscardUnknown: true,
@@ -28,6 +63,7 @@ func NewDecoder() *Decoder {
2863

2964
return &Decoder{
3065
unmarshalOptions: unmarshalOptions,
66+
cache: cache,
3167
}
3268
}
3369

@@ -55,6 +91,7 @@ func (d Decoder) Decode(
5591
if err != nil {
5692
return err
5793
}
94+
5895
return d.DecodeReader(reader, dest)
5996
}
6097

@@ -81,9 +118,15 @@ func (d Decoder) DecodeReader(
81118
if dest == nil {
82119
return gojsondecoder.ErrNilDestination
83120
}
121+
122+
// Read all data from the reader
123+
data, err := io.ReadAll(reader)
124+
if err != nil {
125+
return err
126+
}
84127

85128
return UnmarshalByReflection(
86-
reader,
129+
data,
87130
dest,
88131
&d.unmarshalOptions,
89132
)

decoder/protojson/errors.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package protojson
2+
3+
import (
4+
"errors"
5+
)
6+
7+
const (
8+
ErrFieldNotHandled = "field not handled on decoding: %s"
9+
)
10+
11+
var (
12+
ErrNilMapper = errors.New("decoder mapper is nil")
13+
ErrNilDestinationInstance = errors.New("nil destination instance")
14+
ErrNilDestination = errors.New("nil destination")
15+
)

decoder/protojson/mapper.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package protojson
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"reflect"
7+
8+
goreflect "github.com/ralvarezdev/go-reflect"
9+
gostringsjson "github.com/ralvarezdev/go-strings/json"
10+
"google.golang.org/protobuf/encoding/protojson"
11+
"google.golang.org/protobuf/proto"
12+
)
13+
14+
type (
15+
// Mapper is the struct to hold precomputed marshal by reflection functions
16+
Mapper struct{
17+
reflectType reflect.Type
18+
isProtoMessage bool
19+
regularFields map[string]struct{}
20+
protoMessageFields map[string]struct{}
21+
jsonFieldNames map[string]string
22+
nestedStructs map[string]*Mapper
23+
}
24+
)
25+
26+
// NewMapper creates a new Mapper instance
27+
//
28+
// Parameters:
29+
//
30+
// - destinationInstance: the destination instance to create the mapper for
31+
//
32+
// Returns:
33+
//
34+
// - *Mapper: the new Mapper instance
35+
// - error: the error if any
36+
func NewMapper(
37+
destinationInstance any,
38+
) (*Mapper, error) {
39+
// Check if the destination instance is nil
40+
if destinationInstance == nil {
41+
return nil, ErrNilDestinationInstance
42+
}
43+
44+
// Check if the destination is a proto.Message
45+
_, ok := destinationInstance.(proto.Message)
46+
if ok {
47+
return &Mapper{
48+
isProtoMessage: true,
49+
}, nil
50+
}
51+
52+
// Create the maps to hold field information
53+
regularFields := make(map[string]struct{})
54+
protoMessageFields := make(map[string]struct{})
55+
nestedStructs := make(map[string]*Mapper)
56+
jsonFieldNames := make(map[string]string)
57+
58+
// Get the reflect type and value of the destination instance
59+
reflectType := goreflect.GetDereferencedType(destinationInstance)
60+
reflectValue := goreflect.GetDereferencedValue(destinationInstance)
61+
62+
// Check if the type is a struct
63+
for i := 0; i < reflectValue.NumField(); i++ {
64+
// Get the field and its type
65+
structField := reflectType.Field(i)
66+
fieldValue := reflectValue.Field(i)
67+
fieldName := structField.Name
68+
69+
// Check if the field can be set
70+
if !fieldValue.CanSet() {
71+
continue
72+
}
73+
74+
// Get the JSON tag of the field
75+
jsonTag, err := gostringsjson.GetJSONTag(&structField, fieldName)
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
// Get the JSON field name from the tag
81+
jsonFieldName, err := gostringsjson.GetJSONTagName(jsonTag, fieldName)
82+
if err != nil {
83+
return nil, err
84+
}
85+
86+
// Store the JSON field name
87+
jsonFieldNames[fieldName] = jsonFieldName
88+
89+
// Get the field interface
90+
fieldValueInterface := fieldValue.Interface()
91+
92+
// Check if the field is a proto.Message
93+
if _, ok := fieldValueInterface.(proto.Message); ok {
94+
// Store the proto.Message field
95+
protoMessageFields[fieldName] = struct{}{}
96+
continue
97+
}
98+
99+
// Dereference pointer if necessary
100+
if fieldValue.Kind() == reflect.Ptr {
101+
fieldValue = fieldValue.Elem()
102+
}
103+
104+
// Check if the field is a struct
105+
if fieldValue.Kind() == reflect.Struct {
106+
// Create a nested mapper for the struct field
107+
nestedMapper, err := NewMapper(fieldValueInterface)
108+
if err != nil {
109+
return nil, err
110+
}
111+
nestedStructs[fieldName] = nestedMapper
112+
continue
113+
}
114+
115+
// Regular field
116+
regularFields[fieldName] = struct{}{}
117+
}
118+
return &Mapper{
119+
reflectType: reflectType,
120+
isProtoMessage: false,
121+
regularFields: regularFields,
122+
protoMessageFields: protoMessageFields,
123+
nestedStructs: nestedStructs,
124+
}, nil
125+
}
126+
127+
// UnmarshalByReflection unmarshal JSON data into a destination using reflection
128+
//
129+
// Parameters:
130+
//
131+
// - body: The JSON data to unmarshal
132+
// - dest: The destination to unmarshal the JSON data into
133+
// - unmarshalOptions: Options for unmarshalling proto messages (optional, can be nil)
134+
//
135+
// Returns:
136+
//
137+
// - error: The error if any
138+
func (m *Mapper) UnmarshalByReflection(
139+
body []byte,
140+
dest any,
141+
unmarshalOptions *protojson.UnmarshalOptions,
142+
) error {
143+
// Check if the mapper is nil
144+
if m == nil {
145+
return ErrNilMapper
146+
}
147+
148+
// Check if the destination is nil
149+
if dest == nil {
150+
return ErrNilDestination
151+
}
152+
153+
// Check if the unmarshal options are nil, if so initialize them
154+
if unmarshalOptions == nil {
155+
unmarshalOptions = &protojson.UnmarshalOptions{}
156+
}
157+
158+
// Check if the destination is a proto.Message
159+
if m.isProtoMessage {
160+
// Unmarshal directly into the proto.Message
161+
if unmarshalErr := unmarshalOptions.Unmarshal(
162+
body,
163+
dest.(proto.Message),
164+
); unmarshalErr != nil {
165+
return unmarshalErr
166+
}
167+
return nil
168+
}
169+
170+
// Initialize the map to hold intermediate JSON data
171+
var tempDest map[string]any
172+
if unmarshalErr := json.Unmarshal(body, &tempDest); unmarshalErr != nil {
173+
return unmarshalErr
174+
}
175+
176+
// Get the reflect value of the destination
177+
reflectValue := goreflect.GetDereferencedValue(dest)
178+
179+
// Decode nested proto messages
180+
mappedBody := make(map[string]any)
181+
for i := 0; i < reflectValue.NumField(); i++ {
182+
// Get the field and its type
183+
structField := m.reflectType.Field(i)
184+
fieldName := structField.Name
185+
fieldType := structField.Type
186+
fieldValue := reflectValue.Field(i)
187+
188+
// Get the JSON tag of the field
189+
jsonFieldName, ok := m.jsonFieldNames[fieldName]
190+
if !ok {
191+
// If no JSON field name is found, skip the field, it means it cannot be set
192+
continue
193+
}
194+
195+
// Get the corresponding body field
196+
bodyField, ok := tempDest[jsonFieldName]
197+
if !ok {
198+
continue
199+
}
200+
201+
// Check if the field is a regular field
202+
if _, ok := m.regularFields[fieldName]; ok {
203+
// Directly map the body field
204+
mappedBody[fieldName] = bodyField
205+
continue
206+
}
207+
208+
// Check if the field is a proto.Message
209+
if _, ok := m.protoMessageFields[fieldName]; ok {
210+
// Marshal the body field to JSON
211+
marshaledField, err := json.Marshal(bodyField)
212+
if err != nil {
213+
return err
214+
}
215+
216+
// Create a new instance of the proto.Message if it's a pointer and is nil
217+
if fieldValue.Kind() == reflect.Ptr {
218+
fieldValue.Set(reflect.New(fieldType.Elem()))
219+
}
220+
221+
// Get the interface of the field value
222+
fieldValueInterface := fieldValue.Interface()
223+
224+
// Unmarshal the JSON into the proto.Message field
225+
if unmarshalErr := unmarshalOptions.Unmarshal(
226+
marshaledField,
227+
fieldValueInterface.(proto.Message),
228+
); unmarshalErr != nil {
229+
return unmarshalErr
230+
}
231+
232+
// Map the proto.Message field
233+
mappedBody[fieldName] = fieldValueInterface
234+
continue
235+
}
236+
237+
// Check if the field is a nested struct
238+
if nestedMapper, ok := m.nestedStructs[fieldName]; ok {
239+
// Marshal the body field to JSON
240+
marshaledNestedBody, err := json.Marshal(bodyField)
241+
if err != nil {
242+
return err
243+
}
244+
245+
// Get the interface of the field value
246+
fieldValueInterface := fieldValue.Addr().Interface()
247+
248+
// Unmarshal the body field by reflection
249+
nestedMapper.UnmarshalByReflection(marshaledNestedBody, fieldValueInterface, unmarshalOptions)
250+
251+
// Set the mapped nested struct
252+
mappedBody[fieldName] = fieldValueInterface
253+
continue
254+
}
255+
256+
// The field type is not handled, return an error
257+
return fmt.Errorf(ErrFieldNotHandled, fieldName)
258+
}
259+
260+
// Reflect the destination value
261+
destValue := goreflect.GetDereferencedValue(dest)
262+
263+
// Map the body to the struct field
264+
return goreflect.ValueToReflectStruct(mappedBody, destValue, false)
265+
}

0 commit comments

Comments
 (0)