На самом деле, мне может понадобиться сделать это .... см. Комментарий в ответе @icza ....

я на этоstruct:

type Config struct {
  path string
  id   string
  key  string
  addr string
  size uint64
}

Теперь у меня естьDefaultConfig инициализирован с некоторыми значениями и одним загруженным из файла, скажемFileConfig, Я хочу, чтобы обе структуры объединились, чтобы яConfig с содержанием обеих структур.FileConfig следует переопределить все, что установлено вDefaultConfig, покаFileConfig может быть не все поля установлены, (Почему это? Потому что потенциальный пользователь может не знать значение по умолчанию, поэтому удаление этой записи будет эквивалентно установке по умолчанию - я думаю)

Я думал, что мне нужно отражение для этого:

 func merge(default *Config, file *Config) (*Config) {
  b := reflect.ValueOf(default).Elem()
  o := reflect.ValueOf(file).Elem()

  for i := 0; i < b.NumField(); i++ {
    defaultField := b.Field(i)
    fileField := o.Field(i)
    if defaultField.Interface() != reflect.Zero(fileField.Type()).Interface() {
     defaultField.Set(reflect.ValueOf(fileField.Interface()))
    }
  }

  return default
 }

Здесь я не уверен:

Если рефлексия нужна вообщеТам могут быть более простые способы сделать это

Другая проблема, которую я вижу здесь, заключается в том, что проверка на нулевые значения может быть сложной: что, если переопределяющая структуранамеревается переопределить с нулевым значением? К счастью, я не думаю, что это применимо к моему делу - но это становится функцией, это может стать проблемой позже

Ответы на вопрос(3)

которую я вижу здесь, заключается в том, что проверка на нулевые значения может быть сложной: что, если переопределяющая структура намеревается переопределить нулевое значение?

Если вы не можете использоватьencoding/json как указаноicza или другие кодировщики формата, которые ведут себя аналогично, вы можете использовать два отдельных типа.

type Config struct {
    Path string
    Id   string
    Key  string
    Addr string
    Size uint64
}

type ConfigParams struct {
    Path *string
    Id   *string
    Key  *string
    Addr *string
    Size *uint64
}

Теперь с такой функцией:

func merge(conf *Config, params *ConfigParams)

Вы можете проверить наличие полей, не равных нулю, вparams и разыменуйте указатель, чтобы установить значение в соответствующих полях вconf, Это позволяет вам сбрасывать поля вconf с ненулевыми полями с нулевым значением вparams.

 icza20 нояб. 2017 г., 18:06
Хорошо, я не понял. Теперь понятно.
 faboolous20 нояб. 2017 г., 18:19
На самом деле, мне может понадобиться сделать это .... см. Комментарий в ответе @icza ....
 icza20 нояб. 2017 г., 18:00
В моем примере я показалencoding/json обрабатывает правильно, когда файл содержит нулевое значение, и он переопределит ненулевое значение по умолчанию, и результатом будет нулевое значение, как и предполагалось.
 mkopriva20 нояб. 2017 г., 18:04
Да, я знаю @icza, ваше решение отличное, и я бы порекомендовал его. Что я хотел добавить своим ответом, так это то, чтона случай, если онине можешь используйте ваше решениеВозможно, потому что они не загружают конфигурацию из файла JSON или чего-то еще, они могут подойти к нему по-другому.

Для этой структуры я бы реализовал прямойMerge() метод как:

type Config struct {
  path string
  id   string
  key  string
  addr string
  size uint64
}

func (c *Config) Merge(c2 Config) {
  if c.path == "" {
    c.path = c2.path
  }
  if c.id == "" {
    c.id = c2.id
  }
  if c.path == "" {
    c.path = c2.path
  }
  if c.addr == "" {
    c.addr = c2.addr
  }
  if c.size == 0 {
    c.size = c2.size
  }
}

Это почти тот же объем кода, быстрый и легкий для понимания.

Вы можете покрыть этот метод универсальными тестами, которые используют отражение, чтобы убедиться, что новые поля не остались позади.

В этом суть Go - вы пишете больше, чтобы получить быстрый и легкий для чтения код.

Также вы можете посмотреть вgo generate это сгенерирует метод для вас из определения структуры. Может быть, там что-то уже реализовано и доступно на GitHub? Вот пример кода, который делает нечто подобное:https://github.com/matryer/moq

Также на GitHub есть несколько пакетов, которые, я считаю, делают то, что вы хотите во время выполнения, например:https://github.com/imdario/mergo

 Alexander Trakhimenok20 нояб. 2017 г., 17:10
Вы хотите заглянуть вgo generate это сгенерирует метод для вас из определения структуры. Я обновлю свой ответ, чтобы добавить этот пункт.
 faboolous20 нояб. 2017 г., 17:08
Мне это нравится; тем не менее, нам нужно больше думать об этом, так как нам может потребоваться, чтобы это было более обобщенно ... это сложный проект с различными разделами конфигурации, поэтому решение такого типа может стать очень длинным ... что касается медленной части, оно включено только инициализация, поэтому мы можем жить с более медленным, но более общим решением
Решение Вопроса

Предисловие: encoding/json пакет использует отражение (пакетreflect) для чтения / записи значений, в том числе структур. Другие библиотеки, также использующие отражение (например, реализации TOML и YAML), могут работать аналогичным образом (или даже таким же образом), и, таким образом, принцип, представленный здесь, может также применяться к этим библиотекам. Вы должны проверить это с библиотекой, которую вы используете.

Для простоты в представленном здесь решении используются стандартные библиотекиencoding/json.

Элегантное и «нулевое усилие» решение заключается в использованииencoding/json пакет иunmarshal в значение "подготовленной" конфигурации по умолчанию.

Это обрабатывает все, что вам нужно:

пропущенные значения в конфигурационном файле: применяется по умолчаниюзначение, указанное в файле, переопределяет конфигурацию по умолчанию (что бы это ни было)явное переопределение нулевых значений в файле имеет приоритет (перезаписывает ненулевую конфигурацию по умолчанию)

Для демонстрации мы будем использовать эту конфигурационную структуру:

type Config struct {
    S1 string
    S2 string
    S3 string
    S4 string
    S5 string
}

И конфигурация по умолчанию:

var defConfig = &Config{
    S1: "", // Zero value
    S2: "", // Zero value
    S3: "abc",
    S4: "def",
    S5: "ghi",
}

И скажем, файл содержит следующую конфигурацию:

const fileContent = `{"S2":"file-s2","S3":"","S5":"file-s5"}`

Конфигурация файла переопределяетS2, S3 иS5 поля.

Код для загрузки конфигурации:

conf := new(Config) // New config
*conf = *defConfig  // Initialize with defaults

err := json.NewDecoder(strings.NewReader(fileContent)).Decode(&conf)
if err != nil {
    panic(err)
}

fmt.Printf("%+v", conf)

И вывод (попробуйте наGo Playground):

&{S1: S2:file-s2 S3: S4:def S5:file-s5}

Анализируя результаты:

S1 по умолчанию был равен нулю, отсутствовал в файле, результат равен нулюS2 был равен нулю по умолчанию, был задан в файле, результатом является значение файлаS3 был задан в конфигурации, был переопределен, чтобы быть нулем в файле, результат равен нулюS4 был задан в конфиге, отсутствует в файле, результат является значением по умолчаниюS5 был задан в конфиге, задан в файле, результатом является значение файла
 faboolous20 нояб. 2017 г., 19:26
Оказывается, я могу использовать TOML Reader точно так же, как и JSON Reader ... потрясающе!
 faboolous20 нояб. 2017 г., 18:19
Supercool, но для моего случая есть небольшая оговорка ... файл ДОЛЖЕН быть в синтаксисе TOML, а функция, которая его читает, уже возвращает его какConfig экземпляр .... таким образом, все будет перезаписано этимfileContent экземпляр ... Я не упомянул конкретно этот случай, поэтому я могу принять ваш ответ

Ваш ответ на вопрос