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.
look at the third party libraries: graphql
An example I wrote that may help you:
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/graphql-go/graphql"
)
func main() {
// Schema
fields := graphql.Fields{
"id": &graphql.Field{
Type: graphql.ID,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return 111, nil
},
},
"priority": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return "admin", nil
},
},
"address": &graphql.Field{
Type: graphql.NewObject(graphql.ObjectConfig{
Name: "address",
Fields: graphql.Fields{
"city": &graphql.Field{
Type: graphql.String,
},
"country": &graphql.Field{
Type: graphql.String,
},
},
}),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return map[string]string{
"city":"New York",
"country": "us",
}, nil
},
},
}
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
schema, err := graphql.NewSchema(schemaConfig)
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
// Query
query := `
{
id,
address {
city,country
},
priority
}
`
params := graphql.Params{Schema: schema, RequestString: query}
r := graphql.Do(params)
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
rJSON, _ := json.Marshal(r)
fmt.Printf("%s \n", rJSON)
}
Here is a runnable example https://go.dev/play/p/pHH2iBzCLT-
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.