I am trying to achieve attribute selection on a Rest Resource according to query parameters. API client will provide a query parameter called fields
. Server will return only attributes of the resource mentioned in the query string. Server should return different Partial representation of the Resource according to query parameter. Here are some example requests.
GET /api/person/42/?fields=id,createdAtGET /api/person/42/?fields=address,accountGET /api/person/42/?fields=id,priority,address.city
I tried to go map[string]any
route but it did not go well. I am using MongoDB. When I decode mongo document into map[string]any
field names and types are not matching. Therefore I am trying to create a new struct on the fly.
Here is my attempt:
func main() { query, _ := url.ParseQuery("fields=id,priority,address.city") fields := strings.Split(query.Get("fields"), ",") // TODO: extractFields person := getPerson() // Returns a Person Struct personish := PartialStruct(person, fields) marshalled, _ := json.Marshal(personish) // TODO: err fmt.Println(string(marshalled))}func PartialStruct(original any, fields []string) any { // Is there any alternative to reflect ? originalType := reflect.TypeOf(original) partialFields := make([]reflect.StructField, 0) for _, field := range reflect.VisibleFields(originalType) { queryName := field.Tag.Get("json") // TODO: extractQueryName if slices.Contains(fields, queryName) { partialFields = append(partialFields, field) } } partialType := reflect.StructOf(partialFields) // Is there any alternative to Marshal/Unmarshal? partial := reflect.New(partialType).Interface() marshalled, _ := json.Marshal(original) // TODO: err _ = json.Unmarshal(marshalled, partial) // TODO: err return partial}
Here is a runnable example https://go.dev/play/p/Egomxe5NjEc
Resources are modelled as nested structs. Nested fields will be denoted by a "." dot in the query string.
How can I improve PartialStruct
to handle nested fields such as address.city
?
I am willing to change my direction if there are better ways.