Я использую файлы 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)
Типа заранее не знаю. Вы не можете реализовать Unmarshaler
в интерфейсе, не так ли? Или вы говорите о реализации Unmarshal
для type Config
и настраиваемом назначении ВСЕХ полей в структуре (это больше, чем просто interfaceInstance
)
Да, реализуйте Unmarshaler
для типа Config
, и да, в зависимости от значения ключа "type"
вам придется предварительно выделить, сколько бы полей interfaceInstance
у вас ни было. Если вы используете поля интерфейса, декодер encoding/json
не сможет узнать, какой тип вы там хотите. Если это слишком неудобно, попробуйте переосмыслить дизайн вашего файла конфигурации или типа конфигурации.
... кстати, не используйте имена полей, начинающиеся с нижнего регистра (interfaceInstance
), если вы хотите их кодировать / декодировать. Пользовательские символы, начинающиеся со строчной буквы, не экспортируются. Меняем его на InterfaceInstance
Одна из возможных стратегий - реализовать 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
}
Если вы заранее знаете, что это за тип, например вы знаете, что
config2.X.type
- этоY
, тогда вы можете просто предварительно выделить правильный тип. Если вы заранее не знаете тип, вы можете реализовать интерфейсjson.Unmarshaler
.