Как объединить два значения Go одного и того же типа структуры? - PullRequest
0 голосов
/ 21 декабря 2018

Я хочу создать функцию с именем merge(), которая принимает два значения одной и той же структуры, но любой структуры, и возвращает объединенные значения двух структур.

Я хочу, чтобы первое значение имело приоритет.Например, если есть две структуры a и b, после вызова merge(a,b), если есть поля, которые содержат и a, и b, я хочу, чтобы для него было значение a для этогоданное поле.

Как лучше всего это реализовать?https://play.golang.org/p/7s9PWx26gfz

type cat struct {
name  string
color string
age   int
}

type book struct {
title  string
author string
}

func main() {
c1 := cat{
    name:  "Oscar",
    color: "",
    age:   3,
}

c2 := cat{
    name:  "",
    color: "orange",
    age:   2,
}

c3 := merge(c1, c2)

// want: c3 = cat{
//               name: "Oscar",
//               color: "orange",
//               age: 3,
//       }



// another case...
b1 := book{
    title: "Lord of the Rings",
    author: "John Smith",
}

b2 := book{
    title: "Harry Potter",
    author: "",
}

b3 := merge(b1, b2)

// want: b3 = book{
//               title: "Lord of the Rings",
//               author: "John Smith",
//       }
}

Это то, что я имею до сих пор:

// merges two structs, where a's values take precendence over b's values (a's values will be kept over b's if each field has a value)
func merge(a, b interface{}) (*interface{}, error) {
    var result interface{}
    aFields := reflect.Fields(a)
    bFields := reflect.Fields(b)

    if !reflect.DeepEqual(aFields, bFields) {
        return &result, errors.New("cannot merge structs of different struct types")
    }

    aValOf := reflect.ValueOf(a)
    bValOf := reflect.ValueOf(b)
    resultValOf := reflect.ValueOf(result)
    aValues := make([]interface{}, aValOf.NumField())
    resultValues := make([]interface{}, resultValOf.NumField())

    for i := 0; i < aValOf.NumField(); i++ {
        if reflect.ValueOf(aValues[i]).IsNil() {
            resultValues[i] = bValOf.Field(i).Interface()
            break
        }
        resultValues[i] = aValOf.Field(i).Interface()
    }
    return &result, nil
}

1 Ответ

0 голосов
/ 22 декабря 2018

Используйте пользовательские типы для полей в ваших целевых структурах.

type firstString string
type firstInt int

type cat struct {
    Name  firstString
    Color firstString
    Age   firstInt
}

type book struct {
    Title  firstString
    Author firstString
}

Реализуйте UnMarshalJSON для каждого пользовательского типа таким образом, чтобы они отменяли маршализацию только для целевых значений, которые являются пустыми.

func (fs *firstString) UnmarshalJSON(bytes []byte) error {
   if len(*fs) > 0 {
        return nil
    }
    var s string
    err := json.Unmarshal(bytes, &s)
    if err != nil {
        return err
    }
    *fs = firstString(s)
    return nil
}

func (fi *firstInt) UnmarshalJSON(bytes []byte) error {
    if *fi != 0 {
        return nil
    }
    var i int
    err := json.Unmarshal(bytes, &i)
    if err != nil {
        return err
    }
    *fi = firstInt(i)
    return nil
}

Если ваши данные поступают через JSON, вы можете избежать использованияфункция слияния;просто продолжайте unMarshalling входящий JSON в той же структуре.Если ваши данные уже находятся в отдельных структурах, вы можете использовать JSON в качестве посредника в вашей функции слияния, чтобы абстрагировать все отражения, которые есть в вашем примере.

// merges two structs, where a's values take precendence over b's values (a's values will be kept over b's if each field has a value)
func merge(a, b interface{}) interface{} {

    jb, err := json.Marshal(b)
    if err != nil {
        fmt.Println("Marshal error b:", err)
    }
    err = json.Unmarshal(jb, &a)
    if err != nil {
        fmt.Println("Unmarshal error b-a:", err)
    }

    return a
}

Все вместе в рабочем примере:https://play.golang.org/p/5YO2HCi8f0N

...