diff --git a/homeworks/9_generics_and_reflection/README.md b/homeworks/9_generics_and_reflection/README.md new file mode 100644 index 0000000..2bbdb77 --- /dev/null +++ b/homeworks/9_generics_and_reflection/README.md @@ -0,0 +1,20 @@ +# Домашнее задание №9 +В домашнем задании нужно реализовать сериализацию объекта структуры данных в `.properties` формат с использованием тегов структур и рефлексии. + +При сериализации данных в некоторых случаях могут возникнуть проблемы с пустыми значениями. Например, если у вас есть структура, в которой некоторые поля могут быть не заполнены, и вы сериализуете, то в результате получится объект с пустыми полями. Если это не является ожидаемым поведением, то в нашей реализации можно будет использовать тег `omitempty`, чтобы пропустить пустые поля при сериализации. + +Стуктура для сериализации в `.properties` формат (поподробнее с `.properties` форматом можно ознакомиться [здесь](https://ru.wikipedia.org/wiki/Properties_)): + +```go +type Person struct { + Name string `properties:"name"` + Address string `properties:"address,omitempty"` + Age int `properties:"age"` + Married bool `properties:"married"` +} +``` + +Для выполнения домашнего задания подготовлен шаблон кода и основные тесты, которую помогут проверить корректность реализации конвертации. Шаблона доступен по [ссылке](https://github.com/Balun-courses/deep_go/blob/master/homework/generics_and_reflection/homework_test.go). + +Задание со звездочкой +Выполнять необязательно, но если вы хотите, можете попробовать реализовать обощенную функцию сериализации, которая сможет работать не только со структорой `Person`. \ No newline at end of file diff --git a/homeworks/9_generics_and_reflection/homework_test.go b/homeworks/9_generics_and_reflection/homework_test.go new file mode 100644 index 0000000..c44f52e --- /dev/null +++ b/homeworks/9_generics_and_reflection/homework_test.go @@ -0,0 +1,117 @@ +package main + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +// go test -v homework_test.go + +type Person struct { + Name string `properties:"name"` + Address string `properties:"address,omitempty"` + Age int `properties:"age"` + Married bool `properties:"married"` +} + +type ParsedTag struct { + name string + omitempty bool +} + +const ( + propertiesTag = "properties" + emptyTag = "omitempty" +) + +func Serialize(object any) string { + var sb strings.Builder + t := reflect.TypeOf(object) + v := reflect.ValueOf(object) + for i := 0; i < v.NumField(); i++ { + fieldType := t.Field(i) + fieldValue := v.Field(i) + + tag, ok := fieldType.Tag.Lookup(propertiesTag) + if !ok { + continue + } + + parsedTag := parseTag(tag) + + if fieldValue.IsZero() && parsedTag.omitempty { + continue + } + + if !fieldValue.CanInterface() { + continue + } + value := fieldValue.Interface() + + if i > 0 { + sb.WriteByte('\n') + } + // sb.WriteString(fieldType.Name) + sb.WriteString(parsedTag.name) + sb.WriteByte('=') + fmt.Fprint(&sb, value) + } + + return sb.String() +} + +func parseTag(tag string) ParsedTag { + parsedTag := ParsedTag{} + parts := strings.Split(tag, ",") + + for _, v := range parts { + switch v { + case emptyTag: + parsedTag.omitempty = true + default: + parsedTag.name = v + } + + } + + return parsedTag +} + +func TestSerialization(t *testing.T) { + tests := map[string]struct { + person Person + result string + }{ + "test case with empty fields": { + result: "name=\nage=0\nmarried=false", + }, + "test case with fields": { + person: Person{ + Name: "John Doe", + Age: 30, + Married: true, + }, + result: "name=John Doe\nage=30\nmarried=true", + }, + "test case with omitempty field": { + person: Person{ + Name: "John Doe", + Age: 30, + Married: true, + Address: "Paris", + }, + result: "name=John Doe\naddress=Paris\nage=30\nmarried=true", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + result := Serialize(test.person) + assert.Equal(t, test.result, result) + }) + } +}