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