Best Practice: Interface Deserialization in Struct

1,310 views
Skip to first unread message

lo...@syntropy.io

unread,
Nov 30, 2014, 10:35:00 AM11/30/14
to golan...@googlegroups.com
I am writing logic for a poker engine to deal and manage table state.  I have to support JSON serialization and deserialization because I intent to communicate the state of the table over the wire.  I am having an issue deserializing the Deck interface because it doesn't "remember" the concrete type to use (serialization doesn't have any issues).  Also I need Deck to be an interface so my tests can use prearranged decks.  Here is a code example:

type Deck interface {
   Discard(cards ...*Card)
   Pop() *Card
   PopMulti(n int) []*Card
   Reset()
}
 
type Table struct {
    Deck  Deck  `json:"deck"`
}

My current solution is to add methods to the Deck interface to recreate itself and get the current state (I wanted to purposely make this unavailable).  I looked at database/sql package and adapted the Register pattern.  Here is my current solution:

type Deck interface {
   Discard(cards ...*Card)
   Pop() *Card
   PopMulti(n int) []*Card
   Reset()
   
   
// added to support deserialization
    Cards() []*Card
   Discards() []*Card
   FromCards(cards, discards []*Card) Deck
}

// register the implemenation
func RegisterDeck(d Deck) {
       registeredDeck = d
}

var (
      registeredDeck Deck
)

type Table struct {
    Deck  Deck  `json:"deck"`
}

type tableJSON
struct {
 
Cards []*Card
 
Discards []*Card
}

func
(t *Table) MarshalJSON() ([]byte, error) {
  tJSON := &tableJSON{
   Cards:       t.Deck.Cards(),
    Discards:    t.Deck.Discards(),
 }
 return json.Marshal(tJSON)
}

func (t *Table) UnmarshalJSON(b []byte) error {
  tJSON
:= &tableJSON{}
 if err := json.Unmarshal(b, tJSON); err != nil {
   return err
 }
 t.Deck = registeredDeck.FromCards(tJSON.Cards, tJSON.Discards)
  return nil
}

I am wondering if there is a best practice here or some idiomatic way to solve this problem.  I don't like that I have to change the Deck's interface API just for this purpose, but I am guessing there isn't a way around that.  Thanks!

egon

unread,
Dec 1, 2014, 4:02:05 AM12/1/14
to golan...@googlegroups.com, lo...@syntropy.io
On Sunday, 30 November 2014 17:35:00 UTC+2, lo...@syntropy.io wrote:
I am writing logic for a poker engine to deal and manage table state.  I have to support JSON serialization and deserialization because I intent to communicate the state of the table over the wire.  I am having an issue deserializing the Deck interface because it doesn't "remember" the concrete type to use (serialization doesn't have any issues).  Also I need Deck to be an interface so my tests can use prearranged decks.  

Make Deck a concrete type i.e. add some constructor that allows for specifying the card order. I suspect a lot of code will get simpler from this change.

egon

unread,
Dec 1, 2014, 4:07:11 AM12/1/14
to golan...@googlegroups.com, lo...@syntropy.io
On Monday, 1 December 2014 11:02:05 UTC+2, egon wrote:
On Sunday, 30 November 2014 17:35:00 UTC+2, lo...@syntropy.io wrote:
I am writing logic for a poker engine to deal and manage table state.  I have to support JSON serialization and deserialization because I intent to communicate the state of the table over the wire.  I am having an issue deserializing the Deck interface because it doesn't "remember" the concrete type to use (serialization doesn't have any issues).  Also I need Deck to be an interface so my tests can use prearranged decks.  

Make Deck a concrete type i.e. add some constructor that allows for specifying the card order. I suspect a lot of code will get simpler from this change.

I would probably start with something like:

type Dealer interface { Deal() *Card; Shuffle(d *Deck)  }

type Deck []*Card
func (d *Deck) Pop() *Card {}
func (d *Deck) Discard(c *Card, cs ...*Card) {}

+ Egon

lo...@syntropy.io

unread,
Dec 1, 2014, 9:36:55 AM12/1/14
to golan...@googlegroups.com, lo...@syntropy.io
I actually had:

type Deck []*Card

previously, but I needed to keep track of discards.  Draw games require managing discards because if no more cards are available they are shuffled and put back into the deck.  This could and probably should be handled elsewhere however.  I didn't consider a Dealer interface and that definitely solves the problem and will simplify the API tremendously.  Thank you so much for your feedback!

Its awesome how the "Go way" to solve problems like these can produce such elegant solutions.    

egon

unread,
Dec 1, 2014, 9:42:19 AM12/1/14
to golan...@googlegroups.com, lo...@syntropy.io


On Monday, 1 December 2014 16:36:55 UTC+2, lo...@syntropy.io wrote:
I actually had:

type Deck []*Card

previously, but I needed to keep track of discards.  Draw games require managing discards because if no more cards are available they are shuffled and put back into the deck.  

discarded cards are simply a deck that isn't used for dealing cards. :)

Matthew Zimmerman

unread,
Dec 1, 2014, 1:09:18 PM12/1/14
to egon, golang-nuts, lo...@syntropy.io
I implemented something similar for a pinochole server with computer
players. A pinochle deck is a little different, but the principal is
the same. I too serialized to JSON.

https://github.com/mzimmerman/sdzpinochle/blob/master/pinochle.go#L85
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

lo...@syntropy.io

unread,
Dec 1, 2014, 1:26:57 PM12/1/14
to golan...@googlegroups.com, lo...@syntropy.io
I changed my implementation of Deck but I am still running into the same problem with deserialization.  I can serialize and deserialize the Deck, but not the Dealer interface.  What is the idiomatic way to register an implementation for deserialization for an interface?  I am also running into this with my Player interface:

// Player represents a player at a table.
type Player interface {
ID() string
FromID(id string) (Player, error)
Action() (a Action, chips int)
}

/ RegisterPlayer stores the player implementation for json deserialization.
func RegisterPlayer(p Player) {
registeredPlayer = p
}

var (
// mapping to player implemenation
registeredPlayer Player
)

// PlayerState is the state of a player at a table.
type PlayerState struct {
player    Player
}

type playerStateJSON struct {
ID        string      `json:"id"`
}

// MarshalJSON implements the json.Marshaler interface.
func (state *PlayerState) MarshalJSON() ([]byte, error) {
tpJSON := &playerStateJSON{
ID:        state.Player().ID(),
}
return json.Marshal(tpJSON)
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (state *PlayerState) UnmarshalJSON(b []byte) error {
tpJSON := &playerStateJSON{}
if err := json.Unmarshal(b, tpJSON); err != nil {
return err
}

p, err := registeredPlayer.FromID(tpJSON.ID)
if err != nil {
return fmt.Errorf("table PlayerState json deserialization failed because of player %s FromID - %s", tpJSON.ID, err)
}

state.player = p
return nil
}

I basically run into this same pattern again.  Is there a way to solve this problem in a way thats idiomatic?  

Matthew Zimmerman

unread,
Dec 1, 2014, 1:39:47 PM12/1/14
to lo...@syntropy.io, golang-nuts
I'm afraid I'm not following. An interface specifies a set of methods
that the type needs to implement. So on the face of it, serializing
an interface doesn't really make any sense. What you want to do is
serialize the type that implements the interface. I don't see your
Dealer interface; you might need to post more code.

The gob package has some provisions for this in which it requires
registration of the type so that gob knows which types are available
for it to serialize data into when passed an interface.
http://golang.org/pkg/encoding/gob/#Register

I have two types that implement my Player interface. I serialize this
with gob since this part doesn't need to go over the wire. It's not
part of the protocol and Human/AI state is internal to the server.
https://github.com/mzimmerman/sdzpinochle/blob/master/server/server.go#L70

lo...@syntropy.io

unread,
Dec 1, 2014, 2:01:11 PM12/1/14
to golan...@googlegroups.com, lo...@syntropy.io
http://golang.org/pkg/encoding/gob/#Register seems to be solving the same issue.  I need to register the type that implements an interface so that I can deserialize it from JSON.  I am looking for an idiomatic pattern to accomplish this.  Here is how gob solves it:


If I am understanding the Register function correctly it seems to be solving the same goal but for binary.  

Brendan Tracey

unread,
Dec 1, 2014, 3:39:45 PM12/1/14
to golan...@googlegroups.com, lo...@syntropy.io
When I had to solve this issue, I ended up reimplementing Register as well. You can see how I solved it https://github.com/reggo/reggo/blob/master/common/common.go , and some uses of it in reggo/scale and reggo/loss. Please don't import the code directly, as I make no promises about future compatibility. Additionally, there are a number of problems with it in the general case (the pointer marking isn't necessary, it doesn't deal with recursive types, etc.). I haven't fixed it because it works for my problem and I haven't needed to. It should help you get started.
Reply all
Reply to author
Forward
0 new messages