gmessages/libgm/pblite/serialize.go
2023-07-15 15:49:51 +03:00

142 lines
3.5 KiB
Go

package pblite
/*
in protobuf, a message looks like this:
message SomeMessage {
string stringField1 = 1;
int64 intField = 6;
bytes byteField = 9;
}
but when this function is done serializing this protobuf message into a slice, it should look something like this:
[
"someString",
nil,
nil,
nil,
nil,
6,
nil,
nil,
"\x9\x91\x942"
]
Any integer should be translated into int64, it doesn't matter if it's defined as int32 in the proto schema.
In the finished serialized slice it should be int64.
Let's also take in count where there is a message nested inside a message:
message SomeMessage {
string stringField1 = 1;
NestedMessage1 nestedMessage1 = 2;
int64 intField = 6;
bytes byteField = 9;
}
message NestedMessage1 {
string msg1 = 1;
}
Then the serialized output would be:
[
"someString",
["msg1FieldValue"],
nil,
nil,
nil,
6,
nil,
nil,
"\x9\x91\x942"
]
This means that any slice inside of the current slice, indicates another message nested inside of it.
*/
import (
"encoding/base64"
"encoding/json"
"fmt"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
func serializeOneOrList(fieldDescriptor protoreflect.FieldDescriptor, fieldValue protoreflect.Value) (any, error) {
switch {
case fieldDescriptor.IsList():
var serializedList []any
list := fieldValue.List()
for i := 0; i < list.Len(); i++ {
serialized, err := serializeOne(fieldDescriptor, list.Get(i))
if err != nil {
return nil, err
}
serializedList = append(serializedList, serialized)
}
return serializedList, nil
default:
return serializeOne(fieldDescriptor, fieldValue)
}
}
func serializeOne(fieldDescriptor protoreflect.FieldDescriptor, fieldValue protoreflect.Value) (any, error) {
switch fieldDescriptor.Kind() {
case protoreflect.MessageKind:
serializedMsg, err := Serialize(fieldValue.Message().Interface().ProtoReflect())
if err != nil {
return nil, err
}
return serializedMsg, nil
case protoreflect.BytesKind:
return base64.StdEncoding.EncodeToString(fieldValue.Bytes()), nil
case protoreflect.Int32Kind, protoreflect.Int64Kind:
return fieldValue.Int(), nil
case protoreflect.Uint32Kind, protoreflect.Uint64Kind:
return fieldValue.Uint(), nil
case protoreflect.FloatKind, protoreflect.DoubleKind:
return fieldValue.Float(), nil
case protoreflect.EnumKind:
return int(fieldValue.Enum()), nil
case protoreflect.BoolKind:
return fieldValue.Bool(), nil
case protoreflect.StringKind:
return fieldValue.String(), nil
default:
return nil, fmt.Errorf("unsupported field type %s in %s", fieldDescriptor.Kind(), fieldDescriptor.FullName())
}
}
func Serialize(m protoreflect.Message) ([]any, error) {
maxFieldNumber := 0
for i := 0; i < m.Descriptor().Fields().Len(); i++ {
fieldNumber := int(m.Descriptor().Fields().Get(i).Number())
if fieldNumber > maxFieldNumber {
maxFieldNumber = fieldNumber
}
}
serialized := make([]any, maxFieldNumber)
for i := 0; i < m.Descriptor().Fields().Len(); i++ {
fieldDescriptor := m.Descriptor().Fields().Get(i)
fieldValue := m.Get(fieldDescriptor)
fieldNumber := int(fieldDescriptor.Number())
if !m.Has(fieldDescriptor) {
continue
}
serializedVal, err := serializeOneOrList(fieldDescriptor, fieldValue)
if err != nil {
return nil, err
}
serialized[fieldNumber-1] = serializedVal
}
return serialized, nil
}
func SerializeToJSON(m proto.Message) ([]byte, error) {
serialized, err := Serialize(m.ProtoReflect())
if err != nil {
return nil, err
}
return json.Marshal(serialized)
}