Binding JSON array of different objects to Go entities

Hey Folks!
I have a typescripts background and try to switch to Go now. Unfortunately I have some problems wrapping my head around data modelling without generics and inheritance. I am trying to write an API that handles the following requests:

GET /forms/myform

{
    "title": "Hello",
    "fields": [
        {
           "type": "textField",
            "name": "ip",
            "value": "192.168.1.2",
            "label": "My IP",
            "errorMessage": ""
        },
        {
           "type": "range",
            "name": "interval",
            "value": 1000,
            "label": "SomeInterval",
            "errorMessage": ""
        }
    ]
}

I managed to write a handler for the GET request thanks to the empty interface.

I need to handle Patch requests as well. The request body will contain an object of key, value pairs like so:

{
   "ip": "192.168.1.60",
   "interval": 4020
}

The expected outcome would be the updated form:

{
    "title": "Hello",
    "fields": [
        {
           "type": "textField",
            "name": "ip",
            "value": "192.168.1.60",
            "label": "My IP",
            "errorMessage": ""
        },
        {
           "type": "range",
            "name": "interval",
            "value": 4020,
            "label": "SomeInterval",
            "errorMessage": ""
        }
    ]
}

In case the user sends an invalid request like so:

{
   "ip": "192.168.1.3abc",
   "interval": 50000
}

The expected response would be:

{
    "title": "Hello",
    "fields": [
        {
           "type": "textField",
            "name": "ip",
            "value": "192.168.1.3abc",
            "label": "My IP",
            "errorMessage": "The field ip must be a valid IPv4"
        },
        {
           "type": "range",
            "name": "interval",
            "value": 60000,
            "label": "SomeInterval",
            "errorMessage": "The interval is not allowed to be greater than 5000"
        }
    ]
}

I think you get the point. My main problem is how would one model this in Go? How would I bind the input struct to the slice or array containing the fields. Or would I bind to a RequestStruct and then map this to my fields slice? I have tried different approaches like storing the fields as a struct with an index field and marshalling them to a JSON array in the end and fiddling around with reflection etc. All my approaches seem wrong or feel unnatural. Unfortunately I could not find an example online and I hope that someone more experienced can point me in the right direction.

Thanks

1 Like

This code illustrate how you can decode the input JSON and encode the response. Here it is in the playground so you can run it to see the output.

package main

import (
	"encoding/json"
	"fmt"
)

type Req struct {
	IP       string `json:"ip"`
	Interval int    `json:"interval"`
}

type RspField struct {
	Type         string      `json:"type"`
	Name         string      `json:"name"`
	Value        interface{} `json:"value"`
	Label        string      `json:"label"`
	ErrorMessage string      `json:"errorMessage"`
}

type Rsp struct {
	Title  string     `json:"title"`
	Fields []RspField `json:"fields"`
}

func main() {
	// decoding jsonReq into the Req structure
	jsonReq := []byte(`{ "ip": "192.168.1.3abc", "interval": 50000 }`)
	var req Req
	if err := json.Unmarshal(jsonReq, &req); err != nil {
		panic(err)
	}
	fmt.Println("req:", req)

	// encoding the response in json
	rsp := Rsp{
		Title: "Hello",
		Fields: []RspField{
			{
				Type:         "textField",
				Name:         "ip",
				Value:        "192.168.1.3abc",
				Label:        "My IP",
				ErrorMessage: "The field ip must be a valid IPv4",
			},
			{
				Type:         "range",
				Name:         "interval",
				Value:        60000,
				Label:        "SomeInterval",
				ErrorMessage: "The interval is not allowed to be greater than 5000",
			},
		},
	}
	jsonRsp, err := json.Marshal(&rsp)
	if err != nil {
		panic(err)
	}
	fmt.Println("rsp:", string(jsonRsp))
}
1 Like

I would first try to use existing validation tools, here is one I use: https://github.com/go-playground/validator

If you can’t find what you are looking, you may need to use struct tags to write your own validator. Struct tags and reflection can help you.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.