简体   繁体   中英

How to return a partial struct based on query parameters in Go?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM