Let portal worry about trivial details, say goodbye to boilerplate code (our final goal)!
// keep field A only
c := New(Only("A"))
// keep field B and C of the nested struct A
c := New("A[B,C]")
// exclude field A
c := New(Exclude("A"))
// exclude field B and C of the nested struct A, but other fields of struct A are still selected.
c := New(Exclude("A[B,C]"))
It will override the default tag settings defined in your struct.
See example here.
portal.Dump(&dst, &src, portal.DisableCache())
// Model definition
type UserModel struct {
ID int
}
func (u *UserModel) Fullname() string {
return fmt.Sprintf("user:%d", u.ID)
}
// Fullname2 'attribute' method can accept an ctx param.
func (u *UserModel) Fullname2(ctx context.Context) string {
return fmt.Sprintf("user:%d", u.ID)
}
// Fullname3 'attribute' can return error too, portal will ignore the
// result if error returned.
func (u *UserModel) Fullname3(ctx context.Context) (string, error) {
return fmt.Sprintf("user:%d", u.ID)
}
type BadgeModel {
Name string
}
func (u *UserModel) Badge(ctx context.Context) (*BadgeModel, error) {
return &BadgeModel{
Name: "Cool"
}, nil
}
// Schema definition
type UserSchema struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty" portal:"attr:Fullname"`
// Chaining accessing is also supported.
// portal calls method `UserModel.Badge()`, then accesses Badge.Name field.
BadgeName string `json:"badge_name,omitempty" portal:"attr:Badge.Name"`
}
type TaskSchema struct {
Title string `json:"title,omitempty" portal:"meth:GetTitle"`
Description string `json:"description,omitempty" portal:"meth:GetDescription"`
// Chaining accessing is also supported for method result.
ScheduleAt *field.Timestamp `json:"schedule_at,omitempty" portal:"meth:FetchSchedule.At"`
ScheduleDescription *string `json:"schedule_description,omitempty" portal:"meth:FetchSchedule.Description"`
}
func (ts *TaskSchema) GetTitle(ctx context.Context, model *model.TaskModel) string {
// Accept extra context param.
// TODO: Read info from the `ctx` here.
return "Task Title"
}
func (ts *TaskSchema) GetDescription(model *model.TaskModel) (string, error) {
// Here we ignore the first context param.
// If method returns an error, portal will ignore the result.
return "Custom description", nil
}
type Schedule struct {
At time.Time
Description string
}
func (ts *TaskSchema) FetchSchedule(model *model.TaskModel) *Schedule {
return &Schedule{
Description: "High priority",
At: time.Now(),
}
}
type TaskSchema struct {
Title string `json:"title,omitempty" portal:"meth:GetTitle;async"`
Description string `json:"description,omitempty" portal:"meth:GetDescription;async"`
}
type UserSchema struct {
ID string `json:"id,omitempty"`
}
type TaskSchema struct {
User *UserSchema `json:"user,omitempty" portal:"nested"``
}
type NotiSchema struct {
ID string `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Content string `json:"content,omitempty"`
}
type UserSchema struct {
Notifications []*NotiSchema `json:"notifications,omitempty" portal:"nested;only:id,title"`
AnotherNotifications []*NotiSchema `json:"another_notifications,omitempty" portal:"nested;attr:Notifications;exclude:content"`
}
type UserSchema struct {
Type string `json:"type" portal:"const:vip"`
}
type Student struct {
ID int
}
type info struct {
Name string
Height int
}
func(s *Student) Info() info {
return &info{Name: "name", Height: 180}
}
type StudentSchema struct {
Name string `json:"name" portal:"attr:Info.Name,disablecache"`
Height int `json:"height" portal:"attr:Info.Height,disablecache"`
}
Only works for types: pointer/slice/map. For basic types (integer, string, bool), default value will be converted and set to field directly. For complex types (eg. map/slice/pointer to custom struct), set default to AUTO_INIT
, portal will initialize field to its zero value.
type ContentSchema struct {
BizID *string `json:"biz_id" portal:"default:100"`
SkuID *string `json:"sku_id"` // -> json null
Users []*UserSchema `json:"users" portal:"default:AUTO_INIT"` // -> json []
Members map[string]int `json:"members" portal:"default:AUTO_INIT"` // -> json {}
User *UserSchema `json:"user" portal:"default:AUTO_INIT"`
}
type PersonSchema struct {
ID string `json:"id"`
Age int `json:"age"`
}
type UserSchema2 struct {
PersonSchema // embedded schema
Token string `json:"token"`
}
Custom field type must implements the Valuer
and ValueSetter
interface defined in types.go.
type Timestamp struct {
tm time.Time
}
func (t *Timestamp) SetValue(v interface{}) error {
switch timeValue := v.(type) {
case time.Time:
t.tm = timeValue
case *time.Time:
t.tm = *timeValue
default:
return fmt.Errorf("expect type `time.Time`, not `%T`", v)
}
return nil
}
func (t *Timestamp) Value() (interface{}, error) {
return t.tm, nil
}
func (t *Timestamp) MarshalJSON() ([]byte, error) {
return json.Marshal(t.tm.Unix())
}
func (t *Timestamp) UnmarshalJSON(data []byte) error {
var i int64
if err := json.Unmarshal(data, &i); err != nil {
return err
}
t.tm = time.Unix(i, 0)
return nil
}
Values from functions will be cached for schema fields tagged by ATTR
and METH
. You can choose not to use it by disabling the cache of a single field, a whole schema, or simple for one time Dump
.
type StudentModel struct {
ID int
}
// the Meta might be costful
func (m *StudentModel) Meta() *meta {
time.Sleep(100 * time.Millisecond)
return &meta{ID: 1}
}
type StudentSchema struct {
Name string `json:"name" portal:"attr:Meta.Name"`
Height int `json:"height" portal:"attr:Meta.Height"`
Subjects []*SubjectSchema `json:"subjects" portal:"nested;async"`
// NextID is a Set method, which must not be cached
NextID int `json:"next_id" portal:"meth:SetNextID;disablecache"` // no using cache
}
func (s *StudentSchema) SetNextID(m *StudentModel) int {
return m.ID + 1
}
type SubjectSchema struct {
Name string `json:"name" portal:"meth:GetInfo.Name"`
Teacher string `json:"teacher" portal:"meth:GetInfo.Teacher"`
}
// If you don't want SubjectSchema to use cache, forbidden it by implementing a PortalDisableCache method.
func (s *StudentSchema) PortalDisableCache() bool { return true }
func (s *StudentSchema) GetInfo(m *StudentModel) info {
return info{Name: "subject name", teacher: "teacher"}
}
var m = StudentModel{ID: 1}
var s StudentSchema
// setup cache
portal.SetCache(portal.DefaultCache)
defer portal.SetCache(nil)
// Not using cache for this dump
portal.Dump(&s, &m, portal.DisableCache())
// StudentSchema.NextID and whole SubjectSchema are not using cache
portal.Dump(&s, &m)
Incidently, portal.Cacher interface{} are expected to be implemented if you'd like to replace the portal.DefaultCache and to use your own.