Skip to content

Commit fc49f4d

Browse files
committed
init
0 parents  commit fc49f4d

9 files changed

+496
-0
lines changed

README.MD

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Serializer
2+
3+
This project is heavily inspired by the Symfony Serializer Component.
4+
Its purpose is to facilitate the serialization and deserialization of golang structures in various formats while selectively excluding or including fields using serialization groups.
5+
6+
## Overview
7+
8+
The Serializer library provides a flexible way to serialize and deserialize data structures in multiple formats. It draws inspiration from the Symfony Serializer Component, offering similar functionality in the Go programming language.
9+
10+
## Features
11+
12+
- **Format Support:** Serializer currently supports various serialization formats, including JSON, CSV & XML.
13+
- **Serialization Groups:** You can use serialization groups to selectively include or exclude fields during the serialization process.
14+
- **Inspired by Symfony:** Leveraging concepts from the Symfony Serializer Component ensures familiarity for users familiar with Symfony.
15+
16+
## Installation
17+
18+
To use the Serializer library in your Go project, simply import the package:
19+
20+
```shell
21+
go get github.com/philphil/serializer
22+
```
23+
24+
25+
## Example
26+
```
27+
package main
28+
29+
import (
30+
"fmt"
31+
"github.com/philiphil/serializer"
32+
)
33+
34+
type MyStruct struct {
35+
Name string `json:"name" group:"group1"`
36+
Age int `json:"age" group:"group2"`
37+
Email string `json:"email"`
38+
}
39+
40+
func main() {
41+
// Create an instance of Serializer
42+
mySerializer := serializer.NewSerializer(serializer.Json)
43+
44+
// Data to serialize
45+
dataToSerialize := MyStruct{
46+
Name: "John Doe",
47+
Age: 30,
48+
49+
}
50+
51+
// Serialize data to JSON with the "group1" group
52+
serializedData, err := mySerializer.Serialize(dataToSerialize, "json", "group1")
53+
if err != nil {
54+
fmt.Println("Serialization error:", err)
55+
return
56+
}
57+
58+
fmt.Println("Serialized Data:", serializedData)
59+
60+
// New structure for deserialization
61+
var deserializedData MyStruct
62+
63+
// Deserialize the data
64+
err = mySerializer.Deserialize(serializedData, &deserializedData)
65+
if err != nil {
66+
fmt.Println("Deserialization error:", err)
67+
return
68+
}
69+
70+
// Display the deserialized data
71+
fmt.Printf("Deserialized Data: %+v\n", deserializedData)
72+
}
73+
74+
```

deserialize.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package serializer
2+
3+
import (
4+
"encoding/csv"
5+
"encoding/json"
6+
"encoding/xml"
7+
"fmt"
8+
"reflect"
9+
"strings"
10+
)
11+
12+
func (s *Serializer) Deserialize(data string, obj any) error {
13+
if !isPointer(obj) {
14+
return fmt.Errorf("object must be pointer")
15+
}
16+
switch s.Format {
17+
case JSON:
18+
return json.Unmarshal([]byte(data), obj)
19+
case XML:
20+
return xml.Unmarshal([]byte(data), obj)
21+
case CSV:
22+
return s.deserializeCSV(data, obj)
23+
default:
24+
return fmt.Errorf("Unsupported format: %s", s.Format)
25+
}
26+
}
27+
28+
func (s *Serializer) MergeObjects(target interface{}, source interface{}) error {
29+
targetValue := reflect.ValueOf(target)
30+
sourceValue := reflect.ValueOf(source)
31+
32+
if targetValue.Kind() != reflect.Ptr || sourceValue.Kind() != reflect.Ptr {
33+
return fmt.Errorf("both target and source must be pointers")
34+
}
35+
36+
targetValue = targetValue.Elem()
37+
sourceValue = sourceValue.Elem()
38+
39+
for i := 0; i < targetValue.NumField(); i++ {
40+
targetField := targetValue.Field(i)
41+
sourceField := sourceValue.Field(i)
42+
43+
// Ne fusionner que les champs exportés non vides
44+
if targetField.CanSet() && !isEmpty(sourceField) {
45+
targetField.Set(sourceField)
46+
}
47+
}
48+
49+
return nil
50+
}
51+
52+
func (s *Serializer) DeserializeAndMerge(data string, target interface{}) error {
53+
source := reflect.New(reflect.TypeOf(target).Elem()).Interface()
54+
55+
if err := s.Deserialize(data, source); err != nil {
56+
return err
57+
}
58+
59+
return s.MergeObjects(target, source)
60+
}
61+
62+
func isEmpty(v reflect.Value) bool {
63+
zero := reflect.Zero(v.Type())
64+
return reflect.DeepEqual(v.Interface(), zero.Interface())
65+
}
66+
67+
func isPointer(v interface{}) bool {
68+
t := reflect.TypeOf(v)
69+
return t.Kind() == reflect.Ptr
70+
}
71+
72+
func (s *Serializer) deserializeCSV(data string, obj any) error {
73+
value := reflect.ValueOf(obj)
74+
if value.Kind() != reflect.Ptr || value.IsNil() {
75+
return fmt.Errorf("Invalid object type for CSV deserialization")
76+
}
77+
78+
reader := csv.NewReader(strings.NewReader(data))
79+
rows, err := reader.ReadAll()
80+
if err != nil {
81+
return err
82+
}
83+
84+
elemType := value.Elem().Type()
85+
elem := reflect.New(elemType).Elem()
86+
87+
for _, row := range rows {
88+
for i, fieldValue := range row {
89+
field := elem.Field(i)
90+
if field.IsValid() && field.CanSet() {
91+
field.SetString(fieldValue)
92+
}
93+
}
94+
}
95+
96+
value.Elem().Set(elem)
97+
return nil
98+
}

deserialize_test.go

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package serializer
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestSerializer_Deserialize(t *testing.T) {
9+
s := NewSerializer(JSON)
10+
serialized, err := s.Serialize(test, "test")
11+
if err != nil {
12+
panic(err)
13+
}
14+
o := Test{}
15+
err = s.Deserialize(serialized, &o)
16+
if o != testDeserializedResult {
17+
panic("!")
18+
}
19+
20+
}
21+
22+
func TestSerializer_MergeObjects(t *testing.T) {
23+
target := Test{
24+
11, 11, 11, 11, 11, 11, 11,
25+
}
26+
result := Test{
27+
9, 11, 7, 6, 11, 11, 3,
28+
}
29+
s := NewSerializer(JSON)
30+
serialized, err := s.Serialize(test, "test")
31+
if err != nil {
32+
panic(err)
33+
}
34+
o := Test{}
35+
err = s.Deserialize(serialized, &o)
36+
err = s.MergeObjects(&target, &o)
37+
if err != nil {
38+
panic(err)
39+
}
40+
if target != result {
41+
fmt.Println(target)
42+
fmt.Println(result)
43+
panic("!")
44+
}
45+
46+
}
47+
48+
func TestSerializer_DeserializeAndMerge(t *testing.T) {
49+
target := Test{
50+
11, 11, 11, 11, 11, 11, 11,
51+
}
52+
result := Test{
53+
9, 11, 7, 6, 11, 11, 3,
54+
}
55+
s := NewSerializer(JSON)
56+
serialized, err := s.Serialize(test, "test")
57+
if err != nil {
58+
panic(err)
59+
}
60+
err = s.DeserializeAndMerge(serialized, &target)
61+
if err != nil {
62+
panic(err)
63+
}
64+
if target != result {
65+
fmt.Println(target)
66+
fmt.Println(result)
67+
panic("!")
68+
}
69+
70+
}
71+
72+
type MyStruct struct {
73+
Name string `json:"name" group:"group1"`
74+
Age int `json:"age" group:"group2"`
75+
Email string `json:"email"`
76+
}
77+
78+
func main() {
79+
// Create an instance of Serializer
80+
mySerializer := NewSerializer(JSON)
81+
82+
// Data to serialize
83+
dataToSerialize := MyStruct{
84+
Name: "John Doe",
85+
Age: 30,
86+
87+
}
88+
89+
// Serialize data to JSON with the "group1" group
90+
serializedData, err := mySerializer.Serialize(dataToSerialize, "json", "group1")
91+
if err != nil {
92+
fmt.Println("Serialization error:", err)
93+
return
94+
}
95+
96+
fmt.Println("Serialized Data:", serializedData)
97+
98+
// New structure for deserialization
99+
var deserializedData MyStruct
100+
101+
// Deserialize the data
102+
err = mySerializer.Deserialize(serializedData, &deserializedData)
103+
if err != nil {
104+
fmt.Println("Deserialization error:", err)
105+
return
106+
}
107+
108+
// Display the deserialized data
109+
fmt.Printf("Deserialized Data: %+v\n", deserializedData)
110+
}

filter.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package serializer
2+
3+
import (
4+
"reflect"
5+
"strings"
6+
)
7+
8+
func filterByGroups[T any](obj T, groups ...string) T {
9+
value := reflect.ValueOf(obj)
10+
elemType := value.Type()
11+
12+
var newFields []reflect.StructField
13+
14+
for i := 0; i < elemType.NumField(); i++ {
15+
field := elemType.Field(i)
16+
if isFieldExported(field) && isFieldIncluded(field, groups) {
17+
newFields = append(newFields, field)
18+
}
19+
}
20+
21+
newStructType := reflect.StructOf(newFields)
22+
newValue := reflect.New(newStructType).Elem()
23+
24+
for i, field := range newFields {
25+
fieldName := field.Name
26+
fieldValue := value.FieldByName(fieldName)
27+
newValue.Field(i).Set(fieldValue)
28+
}
29+
30+
return newValue.Interface().(T)
31+
}
32+
33+
func isFieldIncluded(field reflect.StructField, groups []string) bool {
34+
if len(groups) == 0 {
35+
return true //No filtration then
36+
}
37+
38+
tag := field.Tag.Get("group")
39+
if tag == "" {
40+
return false
41+
}
42+
43+
groupList := strings.Split(tag, ",")
44+
for _, group := range groups {
45+
for _, g := range groupList {
46+
if group == g {
47+
return true
48+
}
49+
}
50+
}
51+
52+
return false
53+
}
54+
55+
func isFieldExported(field reflect.StructField) bool {
56+
return field.PkgPath == ""
57+
}

filter_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package serializer
2+
3+
import (
4+
"testing"
5+
)
6+
7+
type Test struct {
8+
Test0 int `group:"test"`
9+
Test1 int `group:"testo"`
10+
Test2 int `group:"test"`
11+
Test3 int `group:"testo,test"`
12+
Test4 int
13+
test5 int
14+
Test6 int `group:"test"`
15+
}
16+
17+
var test = Test{
18+
9, -8, 7, 6, -5, -4, 3,
19+
}
20+
var testDeserializedResult = Test{
21+
9, 0, 7, 6, 0, 0, 3,
22+
}
23+
24+
func TestExecute(t *testing.T) {
25+
s := NewSerializer(JSON)
26+
serialized, err := s.Serialize(test, "test")
27+
if err != nil {
28+
panic(err)
29+
}
30+
o := Test{}
31+
err = s.Deserialize(serialized, &o)
32+
}

format.go

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package serializer
2+
3+
type Format string
4+
5+
const (
6+
undefined Format = "undefined"
7+
JSON = "JSON"
8+
XML = "XML"
9+
CSV = "CSV"
10+
)

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/philiphil/serializer
2+
3+
go 1.19

0 commit comments

Comments
 (0)