1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2024-11-24 17:07:00 +02:00
pocketbase/tools/rest/json_serializer.go
2023-08-23 20:56:38 +03:00

143 lines
3.0 KiB
Go

package rest
import (
"encoding/json"
"strings"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase/tools/list"
"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"
}
statusCode := c.Response().Status
param := c.QueryParam(fieldsParam)
if param == "" || statusCode < 200 || statusCode > 299 {
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
}
if list.ExistInSlice("*", fields) {
// append all missing root level data keys
for k := range data {
var exists bool
for _, f := range fields {
if strings.HasPrefix(f+".", k+".") {
exists = true
break
}
}
if !exists {
fields = append(fields, k)
}
}
}
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)
}
}