Custom config loading in Go - json

I'm using JSON files to store/load my config. Let's say I have the following:
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"`
}
Config file example:
{
"config1": {
"X": {
"type": "Z",
"Value": "value_1"
}
},
"config2": {
"X": {
"type": "Y",
"Value": "value_2"
}
}
}
I want to be able to define config files something like this example, and be able to dynamically load the JSON as either struct Y or struct Z. Any suggestions on how to accomplish this? I'm using a simple json.Decoder to load the JSON as a struct.
decoder := json.NewDecoder(file)
err = decoder.Decode(&config)

One possible strategy would be to implement json.Unmarshaler for the Config type in such a way that you first unmarshal into a generic object and inspect the "type" attribute then, switching on the type string, unmarshal the same byte array into the known type and assign to the "interfaceInstance" member of the config.
For example (Go Playground):
// 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
}

Related

`json.Marshal()` converts an array field of a struct to map which it shouldn't

I want to serialize object of types.Project from github.com/compose-spec into JSON
However, a field of ths struct which is supposed to be array, is always serialized into map.
package main
import (
"encoding/json"
types "github.com/compose-spec/compose-go/types"
)
func main() {
out := types.Project{
Name: "foo",
Services: []types.ServiceConfig{ // <- notice this field is an array
{
Name: "bar",
Image: "hello-world",
},
},
}
buf, err := json.Marshal(out)
if err != nil {
panic(err)
}
println(string(buf)) // <- notice the Services field now a map, which is incorrect!
var in types.Project
if err := json.Unmarshal(buf, &in); err != nil {
panic(err)
}
}
The code fails to run:
{"name":"foo","services":{"bar":{"command":null,"entrypoint":null,"image":"hello-world"}}}
panic: json: cannot unmarshal object into Go struct field Project.services of type types.Services
goroutine 1 [running]:
main.main()
/tmp/sandbox1081064727/prog.go:29 +0x168
The out object is serialized as
{
"name": "foo",
"services": {
"bar": {
"command": null,
"entrypoint": null,
"image": "hello-world"
}
}
}
which should really be something like
{
"name": "foo",
"services": [
{
"name": "bar",
"command": null,
"entrypoint": null,
"image": "hello-world"
}
]
}
The behavior you're seeing is correct with respect to the compose file specification, which says:
A Compose file MUST declare a services root element as a map whose keys are string representations of service names, and whose values are service definitions.
The transformation of the list to a map is implemented by the custom marshalers in compose-go/types.go:
// MarshalYAML makes Services implement yaml.Marshaller
func (s Services) MarshalYAML() (interface{}, error) {
services := map[string]ServiceConfig{}
for _, service := range s {
services[service.Name] = service
}
return services, nil
}
// MarshalJSON makes Services implement json.Marshaler
func (s Services) MarshalJSON() ([]byte, error) {
data, err := s.MarshalYAML()
if err != nil {
return nil, err
}
return json.MarshalIndent(data, "", " ")
}

Golang unmarshaling JSON to protobuf generated structs

I would like to reqeive a JSON response withing a client application and unmarshal this response into a struct. To ensure that the struct stays the same accross all client apps using this package, I would like to define the JSON responses as protobuf messages. I am having difficulties unmarshaling the JSON to the protobuf generated structs.
I have the following JSON data:
[
{
"name": "C1",
"type": "docker"
},
{
"name": "C2",
"type": "docker"
}
]
I have modeled my protobuf definitions like this:
syntax = "proto3";
package main;
message Container {
string name = 1;
string type = 2;
}
message Containers {
repeated Container containers = 1;
}
Using this pattern with structs normaly works, but for some reason using these proto definitions causes issues. The below code demonstrates a working and a non-working example. Although one of the versions work, I am unable to use this solution, since []*Container does not satisfy the proto.Message interface.
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/gogo/protobuf/jsonpb"
)
func working(data string) ([]*Container, error) {
var cs []*Container
return cs, json.Unmarshal([]byte(data), &cs)
}
func notWorking(data string) (*Containers, error) {
c := &Containers{}
jsm := jsonpb.Unmarshaler{}
if err := jsm.Unmarshal(strings.NewReader(data), c); err != nil {
return nil, err
}
return c, nil
}
func main() {
data := `
[
{
"name": "C1",
"type": "docker"
},
{
"name": "C2",
"type": "docker"
}
]`
w, err := working(data)
if err != nil {
panic(err)
}
fmt.Print(w)
nw, err := notWorking(data)
if err != nil {
panic(err)
}
fmt.Print(nw.Containers)
}
Running this gives the following output:
[name:"C1" type:"docker" name:"C2" type:"docker" ]
panic: json: cannot unmarshal array into Go value of type map[string]json.RawMessage
goroutine 1 [running]:
main.main()
/Users/example/go/src/github.com/example/example/main.go:46 +0x1ee
Process finished with exit code 2
Is there a way to unmarshal this JSON to Containers? Or alternatively, make []*Container to satisfy the proto.Message interface?
For the message Containers, i.e.
message Containers {
repeated Container containers = 1;
}
The correct JSON should look like:
{
"containers" : [
{
"name": "C1",
"type": "docker"
},
{
"name": "C2",
"type": "docker"
}
]
}
If you cannot change the JSON then you can utilize the func that you've created
func working(data string) ([]*Container, error) {
var cs []*Container
err := json.Unmarshal([]byte(data), &cs)
// handle the error here
return &Containers{
containers: cs,
}, nil
}
You should use NewDecoder to transfer the data to jsonDecoder and then traverse
the array.The code is this
func main() {
data := `
[
{
"name": "C1",
"type": "docker"
},
{
"name": "C2",
"type": "docker"
}
]`
jsonDecoder := json.NewDecoder(strings.NewReader(data))
_, err := jsonDecoder.Token()
if err != nil {
log.Fatal(err)
}
var protoMessages []*pb.Container
for jsonDecoder.More() {
protoMessage := pb.Container{}
err := jsonpb.UnmarshalNext(jsonDecoder, &protoMessage)
if err != nil {
log.Fatal(err)
}
protoMessages = append(protoMessages, &protoMessage)
}
fmt.Println("%s", protoMessages)
}

Unmarshalling dynamic JSON, ignoring known fields

I am trying to unmarshal JSON of the following format:
{
"fixedString": {
"uselessStuff": {},
"alsoUseless": {},
"dynamicField": [
{ "name": "jack" }
],
"dynamicToo": [
{ "name": "jill" }
]
}
}
I would like to drop the fields "uselessStuff" and "alsoUseless", and get everything else. The other keys are user-defined and can take any value.
I can remove the fields I don't want using a custom UnmarshalJSON (based on this answer), but I have a feeling that this is unnecessarily complicated:
package main
import (
"encoding/json"
"fmt"
)
type Response struct {
Things map[string]interface{} `json:"fixedString"`
}
type _R Response
func (f *Response) UnmarshalJSON(bs []byte) (err error) {
foo := _R{}
if err = json.Unmarshal(bs, &foo); err == nil {
*f = Response(foo)
}
delete(f.Things, "uselessStuff")
delete(f.Things, "alsoUseless")
return err
}
func main() {
j := []byte(`{ "fixedString": { "uselessStuff": {}, "alsoUseless": {}, "dynamicField": [ { "name": "jack" } ], "dynamicToo": [ { "name": "jill" } ] } }`)
var r Response
err := json.Unmarshal(j, &r)
if err != nil {
panic(err.Error())
}
for x, y := range r.Things {
fmt.Println(x, y)
}
}
Is there a way to ignore those two keys using annotations, rather than deleting them in a custom UnmarshalJSON function (and having to add the extra type alias _R to avoid a stack overflow)?
You could remove your "uselessStuff" and "alsoUseless" from the map and use them as unexported (lowercase) fields in your struct. Most likely not interface{}
json package ignores unexported fields
type Response struct {
Things map[string]interface{} `json:"fixedString"`
uselessStuff interface{}
alsoUseless interface{}
}

Unmarshalling json arrays as json objects

I have to unmarshal a series of Json objects, but one of the objects contain a json array which is not really structured in a good way.
"labels": [
{
"key": "owner",
"value": "harry"
},
{
"key": "group",
"value": "student"
}
]
I am unmarshalling it using this struct -
type StudentDetails struct {
Id string `json:"id"`
Name string `json:"name"`
Labels []Label `json:"labels,omitempty"`
}
type Label struct {
Key string `json:"key"`
Value string `json:"value"`
}
And I have to access it using x.Labels[0].key == "owner" inside a for loop which is very annoying.
I want to be able to do x.Labels.Owner == "harry" instead. How do I go about achieving this? The rest of JSON is unmarshalled fine using the default unmarshal function, so I don't think writing custom function will be good option.
With the constraints you have here, this is about as close as you will get (run in playground):
package main
import (
"encoding/json"
"fmt"
)
func main() {
j := `
{
"id": "42",
"name": "Marvin",
"labels": [
{
"key": "owner",
"value": "harry"
},
{
"key": "group",
"value": "student"
}
]
}`
d := StudentDetails{}
err := json.Unmarshal([]byte(j), &d)
if err != nil {
panic(err)
}
fmt.Println(d.Labels["owner"])
fmt.Println(d.Labels["group"])
}
type StudentDetails struct {
Id string `json:"id"`
Name string `json:"name"`
Labels Labels `json:"labels"`
}
type Labels map[string]string
func (l *Labels) UnmarshalJSON(b []byte) error {
a := []map[string]string{}
err := json.Unmarshal(b, &a)
if err != nil {
return err
}
t := map[string]string{}
for _, m := range a {
t[m["key"]] = m["value"]
}
*l = t
return nil
}
How about to define custom []Label type and add function on it.
For instance
type Labels []Label
func (l Labels) Owner() string {
if len(l) > 1 {
return l[0].Value
}
return ""
}

Specify struct format in json.Marshal()

I have the following structs, which I use to communicate with an API:
type Object struct {
Id uint64
Type string
Class string
Properties []Property
}
type Property struct {
Name string
DataType string
Value interface{}
}
And I use json.MarshalIndent() to convert my struct into a json before sending it. this gives me something like:
{
"Id": 15,
"Type": "Node",
"Class": "Persona",
"Properties": [
{
"Name": "Nombre",
"DataType": "text",
"Value": "Oso"
},
{
"Name": "Edad",
"DataType": "int",
"Value": 45
},
{
"Name": "Fecha de Naciemiento",
"DataType": "date",
"Value": "1989-09-27T05:30:08-06:00"
}
]
}
I want to format the value value (because it is of type interface{} I need to format it depending on the value type) of the struct Property before marshaling it.
The first solution that occurred to me was to create a (Object) encode() string function or something, that iterates through []Property formatting the values, and marshaling each property separately, then reconstructing the Object with an []string instead of the []Property and then marshaling the object.
Is there any built-in way of doing this? If not, is there any idiomatic way of doing it?
The JSON encoder marshals interface{} values according to the actual type of the value. You can override the default encoding in a couple of ways.
The first is to create a wrapper around values to control how they are encoded using the Marshaler interface. Here's a wrapper that changes how integers are encoded:
type Value struct{ Value interface{} }
func (v Value) MarshalJSON() ([]byte, error) {
switch v := v.Value.(type) {
case int:
return []byte(fmt.Sprintf("\"#%d\"", v)), nil
default:
return json.Marshal(v)
}
}
Use it like this:
prop.Value = Value{45}
playground
A second approach is to implement the Marshaler on the Property type to override how all of the property is marshaled including the Value field.
func (p Property) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.WriteString(`{"Name":`)
d, err := json.Marshal(p.Name)
if err != nil {
return nil, err
}
buf.Write(d)
buf.WriteString(`,"DataType":`)
d, err = json.Marshal(p.DataType)
if err != nil {
return nil, err
}
buf.Write(d)
buf.WriteString(`, "Value":`)
switch v := p.Value.(type) {
case int:
fmt.Fprintf(&buf, "\"#%d\"", v)
default:
d, err := json.Marshal(v)
if err != nil {
return nil, err
}
buf.Write(d)
}
buf.WriteString("}")
return buf.Bytes(), nil
}
playground