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,createdAt
GET /api/person/42/?fields=address,account
GET /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.