将切片结构保存到Cloud Datastore(数据存储模式下的Firestore)的正确方法是什么?

I want to save a slice of structs in Google Cloud Datastore (Firestore in Datastore mode).

Take this Phonebook and Contact for example.

type Contact struct {
  Key         *datastore.Key `json:"id" datastore:"__key__"`
  Email       string         `json:"email" datastore:",noindex"`
  Name        string         `json:"name" datastore:",noindex"`
}
type Phonebook struct {
  Contacts []Contact
  Title    string
}

Saving and loading this struct is no problem as the Datastore library takes care of it.

Due to the presence of some complex properties in my actual code, I need to implement PropertyLoadSaver methods.

Saving the Title property is straightforward. But I have problems storing the slice of Contact structs.

I tried using the SaveStruct method:

func (pb *Phonebook) Save() ([]datastore.Property, error) {
  ps := []datastore.Property{
    {
      Name:    "Title",
      Value:   pb.Title,
      NoIndex: true,
    },
  }
  ctt, err := datastore.SaveStruct(pb.Contacts)
  if err != nil {
    return nil, err
  }
  ps = append(ps, datastore.Property{
    Name:    "Contacts",
    Value:   ctt,
    NoIndex: true,
  })

  return ps, nil
}

This code compiles but doesn't work.

The error message is datastore: invalid entity type

Making a slice of Property explicitly also does not work:

func (pb *Phonebook) Save() ([]datastore.Property, error) {
  ps := []datastore.Property{
    {
      Name:    "Title",
      Value:   pb.Title,
      NoIndex: true,
    },
  }
  cttProps := datastore.Property{
    Name:    "Contacts",
    NoIndex: true,
  }
  if len(pb.Contacts) > 0 {
    props := make([]interface{}, 0, len(pb.Contacts))
    for _, contact := range pb.Contacts {
      ctt, err := datastore.SaveStruct(contact)
      if err != nil {
        return nil, err
      }
      props = append(props, ctt)
    }
    cttProps.Value = props
  }
  ps = append(ps, cttProps)

  return ps, nil
}

Making a slice of Entity does not work either:

func (pb *Phonebook) Save() ([]datastore.Property, error) {
  ps := []datastore.Property{
    {
      Name:    "Title",
      Value:   pb.Title,
      NoIndex: true,
    },
  }
  cttProps := datastore.Property{
    Name:    "Contacts",
    NoIndex: true,
  }
  if len(pb.Contacts) > 0 {
    values := make([]datastore.Entity, len(pb.Contacts))
    props := make([]interface{}, 0, len(pb.Contacts))
    for _, contact := range pb.Contacts {
      ctt, err := datastore.SaveStruct(contact)
      if err != nil {
        return nil, err
      }
      values = append(values, datastore.Entity{
        Properties: ctt,
      })
    }
    for _, v := range values {
      props = append(props, v)
    }
    cttProps.Value = props
  }
  ps = append(ps, cttProps)

  return ps, nil
}

Both yielded the same error datastore: invalid entity type

Finally I resorted to using JSON. The slice of Contact is converted into a JSON array.

func (pb *Phonebook) Save() ([]datastore.Property, error) {
  ps := []datastore.Property{
    {
      Name:    "Title",
      Value:   pb.Title,
      NoIndex: true,
    },
  }
  var values []byte
  if len(pb.Contacts) > 0 {
    js, err := json.Marshal(pb.Contacts)
    if err != nil {
      return nil, err
    }
    values = js
  }
  ps = append(ps, datastore.Property{
    Name:    "Contacts",
    Value:   values,
    NoIndex: true,
  })

  return ps, nil
}

Isn't there a better way of doing this other than using JSON?

I found this document and it mentions src must be a struct pointer.

The only reason you seem to customize the saving of PhoneBook seems to be to avoid saving the Contacts slice if there are no contacts. If so, you can just define your PhoneBook as follows and directly use SaveStruct on the PhoneBook object.

type Phonebook struct {
  Contacts []Contact `datastore:"Contacts,noindex,omitempty"`
  Title    string `datastore:"Title,noindex"`
}