Загрузка пользовательской конфигурации в Go

Я использую файлы JSON для хранения / загрузки своей конфигурации. Допустим, у меня есть следующее:

type X interface

// implements interface X
type Y struct {
    Value string
}

// implements interface X
type Z struct {
    Value string
}

type Config struct {
    interfaceInstance X `json:"X"`
}

Пример файла конфигурации:

{
  "config1": {
    "X": {
      "type": "Z",
      "Value": "value_1"
    }
  },


  "config2": {
    "X": {
      "type": "Y",
      "Value": "value_2"
    }
  }
}

Я хочу иметь возможность определять файлы конфигурации примерно так, как в этом примере, и иметь возможность динамически загружать JSON как структуру Y или структуру Z. Любые предложения о том, как это сделать? Я использую простой json.Decoder для загрузки JSON как структуры.

decoder := json.NewDecoder(file)
err = decoder.Decode(&config)

Если вы заранее знаете, что это за тип, например вы знаете, что config2.X.type - это Y, тогда вы можете просто предварительно выделить правильный тип. Если вы заранее не знаете тип, вы можете реализовать интерфейс json.Unmarshaler.

mkopriva 01.05.2018 08:49

Типа заранее не знаю. Вы не можете реализовать Unmarshaler в интерфейсе, не так ли? Или вы говорите о реализации Unmarshal для type Config и настраиваемом назначении ВСЕХ полей в структуре (это больше, чем просто interfaceInstance)

Nishant Roy 01.05.2018 09:10

Да, реализуйте Unmarshaler для типа Config, и да, в зависимости от значения ключа "type" вам придется предварительно выделить, сколько бы полей interfaceInstance у вас ни было. Если вы используете поля интерфейса, декодер encoding/json не сможет узнать, какой тип вы там хотите. Если это слишком неудобно, попробуйте переосмыслить дизайн вашего файла конфигурации или типа конфигурации.

mkopriva 01.05.2018 09:22

... кстати, не используйте имена полей, начинающиеся с нижнего регистра (interfaceInstance), если вы хотите их кодировать / декодировать. Пользовательские символы, начинающиеся со строчной буквы, не экспортируются. Меняем его на InterfaceInstance

mkopriva 01.05.2018 09:26
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
1
4
151
1

Ответы 1

Одна из возможных стратегий - реализовать json.Unmarshaler для типа Config таким образом, чтобы сначала демаршалировать в общий объект и проверять атрибут «тип», затем, включая строку типа, демаршалировать тот же массив байтов в известный тип и назначать в член "interfaceInstance" конфигурации.

Например (Перейти на игровую площадку):

// Note the slightly different JSON here...
var jsonstr = `{
  "config1": {
    "type": "Z",
    "Value": "value_1"
  },
  "config2": {
    "type": "Y",
    "Value": "value_2"
  }
}`

func main() {
  config := map[string]Config{}
  err := json.Unmarshal([]byte(jsonstr), &config)
  if err != nil {
    panic(err)
  }

  fmt.Printf("OK: %#v\n", config)
  // OK: map[string]main.Config{
  //   "config1": main.Config{interfaceInstance:main.Z{Value:"value_1"}},
  //   "config2": main.Config{interfaceInstance:main.Y{Value:"value_2"}},
  // }
}

func (c *Config) UnmarshalJSON(bs []byte) error {
  // Unmarshal into an object to inspect the type.
  var obj map[string]interface{}
  err := json.Unmarshal(bs, &obj)
  if err != nil {
    return err
  }

  // Unmarshal again into the target type.
  configType := obj["type"].(string)
  switch configType {
  case "Y":
    var y Y
    if err = json.Unmarshal(bs, &y); err == nil {
      c.interfaceInstance = y
    }
  case "Z":
    var z Z
    if err = json.Unmarshal(bs, &z); err == nil {
      c.interfaceInstance = z
    }
  default:
    return fmt.Errorf("unexpected type %q", configType)
  }
  return err
}

Другие вопросы по теме