added ?fields query parameter support to limit the returned api fields
This commit is contained in:
121
tools/rest/json_serializer.go
Normal file
121
tools/rest/json_serializer.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/tools/search"
|
||||
)
|
||||
|
||||
// Serializer represents custom REST JSON serializer based on echo.DefaultJSONSerializer,
|
||||
// with support for additional generic response data transformation (eg. fields picker).
|
||||
type Serializer struct {
|
||||
echo.DefaultJSONSerializer
|
||||
|
||||
FieldsParam string
|
||||
}
|
||||
|
||||
// Serialize converts an interface into a json and writes it to the response.
|
||||
//
|
||||
// It also provides a generic response data fields picker via the FieldsParam query parameter (default to "fields").
|
||||
func (s *Serializer) Serialize(c echo.Context, i any, indent string) error {
|
||||
fieldsParam := s.FieldsParam
|
||||
if fieldsParam == "" {
|
||||
fieldsParam = "fields"
|
||||
}
|
||||
|
||||
param := c.QueryParam(fieldsParam)
|
||||
if param == "" {
|
||||
return s.DefaultJSONSerializer.Serialize(c, i, indent)
|
||||
}
|
||||
|
||||
fields := strings.Split(param, ",")
|
||||
for i, f := range fields {
|
||||
fields[i] = strings.TrimSpace(f)
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var decoded any
|
||||
|
||||
if err := json.Unmarshal(encoded, &decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var isSearchResult bool
|
||||
|
||||
switch i.(type) {
|
||||
case search.Result, *search.Result:
|
||||
isSearchResult = true
|
||||
}
|
||||
|
||||
if isSearchResult {
|
||||
if decodedMap, ok := decoded.(map[string]any); ok {
|
||||
pickFields(decodedMap["items"], fields)
|
||||
}
|
||||
} else {
|
||||
pickFields(decoded, fields)
|
||||
}
|
||||
|
||||
return s.DefaultJSONSerializer.Serialize(c, decoded, indent)
|
||||
}
|
||||
|
||||
func pickFields(data any, fields []string) {
|
||||
switch v := data.(type) {
|
||||
case map[string]any:
|
||||
pickMapFields(v, fields)
|
||||
case []map[string]any:
|
||||
for _, item := range v {
|
||||
pickMapFields(item, fields)
|
||||
}
|
||||
case []any:
|
||||
if len(v) == 0 {
|
||||
return // nothing to pick
|
||||
}
|
||||
|
||||
if _, ok := v[0].(map[string]any); !ok {
|
||||
return // for now ignore non-map values
|
||||
}
|
||||
|
||||
for _, item := range v {
|
||||
pickMapFields(item.(map[string]any), fields)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pickMapFields(data map[string]any, fields []string) {
|
||||
if len(fields) == 0 {
|
||||
return // nothing to pick
|
||||
}
|
||||
|
||||
DataLoop:
|
||||
for k := range data {
|
||||
matchingFields := make([]string, 0, len(fields))
|
||||
for _, f := range fields {
|
||||
if strings.HasPrefix(f+".", k+".") {
|
||||
matchingFields = append(matchingFields, f)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(matchingFields) == 0 {
|
||||
delete(data, k)
|
||||
continue DataLoop
|
||||
}
|
||||
|
||||
// trim the key from the fields
|
||||
for i, v := range matchingFields {
|
||||
trimmed := strings.TrimSuffix(strings.TrimPrefix(v+".", k+"."), ".")
|
||||
if trimmed == "" {
|
||||
continue DataLoop
|
||||
}
|
||||
matchingFields[i] = trimmed
|
||||
}
|
||||
|
||||
pickFields(data[k], matchingFields)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user