How can I deal with hidden data with json.(Un)Marshal

611 views
Skip to first unread message

beatgammit

unread,
Jun 17, 2014, 2:17:27 PM6/17/14
to golan...@googlegroups.com
I have a struct like this:

    type Account struct {
        Username string
        Password string
        // lots of other fields
    }

When a user creates an account, I need to get the password. This password is required for communicating with an external service (it doesn't support OAuth or anything like that) in the background on behalf of the user. These fields need to be saved in the database, but shouldn't be accessible through the API. I understand that this isn't a good security model, but that's beside the point.

I would like to just unmarshal into my struct on input (so users can update fields) and marshal my struct on output (so the user can see everything but hidden fields), however this does not seem to be possible.

I created issue 7337 to add (Un)MarshalTag (takes a variadic list of tags to consider), which was rejected on the grounds that it promotes an undesirable idiom. I understand the sentiment, and it doesn't seem likely to be reconsidered, so I'd like to discuss alternatives.

In my case, I have three different cases I need to account for:
  • user input
  • displaying data to the user
  • storing data in the database

Basically I'm trying to implement set-only and view-only struct fields cleanly. I'm using a document store using JSON as the data format, which is why I'm trying to get encoding/json to do what I want.

I've come up with couple approaches:

  • use tags and pass struct into a function that produces a map[string]interface{} recursively, which is then passed into json.Marshal
    • Unmarshal will work in reverse (unmarshal into map[string]interface{}, recursively update a struct
    • ignores the sentiment that this type of tag use abuses the feature
    • is computationally more expensive because every (Un)Marshal has to do a reflection pass first
  • have a struct definition for each interface (set API, DB, view API) that each implement (Un)MarshalJSON, and one master struct that gets updated
    • adding or deleting a field requires changes in multiple places, potentially error prone
    • adding a different serialization is more difficult and changes must be made in multiple places

Has anyone else run into a similar problem?

Shawn Milochik

unread,
Jun 17, 2014, 2:27:11 PM6/17/14
to golan...@googlegroups.com
How about composition?


Something like this avoids declaring anything twice. Then you use an (unexported) account struct for your internal plumbing and an exported User struct for your client-facing stuff.


egon

unread,
Jun 18, 2014, 3:29:01 AM6/18/14
to golan...@googlegroups.com


On Tuesday, 17 June 2014 21:17:27 UTC+3, beatgammit wrote:
I have a struct like this:

    type Account struct {
        Username string
        Password string 
        // lots of other fields
    }

When a user creates an account, I need to get the password. This password is required for communicating with an external service (it doesn't support OAuth or anything like that) in the background on behalf of the user. These fields need to be saved in the database, but shouldn't be accessible through the API. I understand that this isn't a good security model, but that's beside the point.

I would like to just unmarshal into my struct on input (so users can update fields) and marshal my struct on output (so the user can see everything but hidden fields), however this does not seem to be possible.

I created issue 7337 to add (Un)MarshalTag (takes a variadic list of tags to consider), which was rejected on the grounds that it promotes an undesirable idiom. I understand the sentiment, and it doesn't seem likely to be reconsidered, so I'd like to discuss alternatives.

In my case, I have three different cases I need to account for:
  • user input
  • displaying data to the user
  • storing data in the database

Basically I'm trying to implement set-only and view-only struct fields cleanly. I'm using a document store using JSON as the data format, which is why I'm trying to get encoding/json to do what I want.

You are trying to fit different models into a single model, which means you almost certainly cannot find a solution that satisfied all needs with a single struct.

I've come up with couple approaches:

  • use tags and pass struct into a function that produces a map[string]interface{} recursively, which is then passed into json.Marshal
    • Unmarshal will work in reverse (unmarshal into map[string]interface{}, recursively update a struct
    • ignores the sentiment that this type of tag use abuses the feature
    • is computationally more expensive because every (Un)Marshal has to do a reflection pass first 
  • have a struct definition for each interface (set API, DB, view API) that each implement (Un)MarshalJSON, and one master struct that gets updated
    • adding or deleting a field requires changes in multiple places, potentially error prone
    • adding a different serialization is more difficult and changes must be made in multiple places
This sounds reasonable. Why do you implement the (Un)MarshalJSON in this case? Since you have a struct per use, there's no need to implement it manually.

It shouldn't be difficult to get it working:

type Account struct {
Username string
Password []byte
...
}

type AccountView struct { Username string `json:"username"` }
func (a *Account) GetInfo() AccountView { return AccountView{ Username: a.Username } }
func (a *Account) SetInfo(i AccountView)  { a.Username = i.Username }

And then serialize/unserialize the AccountView as needed.

+ egon
Reply all
Reply to author
Forward
0 new messages