What's a clean solution for supporting dynamic struct fields in Go?

huangapple go评论83阅读模式
英文:

What's a clean solution for supporting dynamic struct fields in Go?

问题

计划重构去年构建的应用程序的代码库。假设我们有一个支持包含相关资源的 API 端点 GET /api/leagues?include=sport。在 API 响应中,有没有更简洁的方法使 league 资源的 sport 属性动态化?sport 属性的值可以是整数或 Sport 结构。目前,我们正在这样做:

// models/league.go
type League struct {
  SportId        int64       `json:"-"`
  Sport          Sport       `json:"-"`
  SportWrapper   interface{} `json:"sport"`
}

// controllers/league.go
include := c.Request.URL.Query().Get("include")
includes := strings.Split(include, ",")

for _, include := range includes {
  if include == "sport" {
    includeSport = true
  }
}

if includeSport {
  league.SportWrapper = league.Sport
} else {
  league.SportWrapper = league.SportId
}

c.JSON(http.StatusOK, league)

API 请求的响应:

{
  "sport": {
    "id": "<id>",
    "name": "<name>"
  }
}

也可以是:

{
  "sport": "<sport_id>"
}

希望这能帮到你!

英文:

Planning to refactor the codebase of an app we built last year. Let's say we have this api endpoint w/c supports including a related resource GET /api/leagues?include=sport. Is there a cleaner way to make the sport attribute of the league resource dynamic in the api response? The value of the sport attribute can be an int or Sport struct. Currently, this is what we're doing:

// models/league.go
type League struct {
  SportId int64 `json:&quot;-&quot;`
  Sport Sport `json:&quot;-&quot;`
  SportWrapper interface{} `json:&quot;sport&quot;`
}

// controllers/league.go
include := c.Request.URL.Query().Get(&quot;include&quot;)
includes := strings.Split(include, &quot;,&quot;)

for _, include := range includes {
  if include == &quot;sport&quot; {
    includeSport = true
  }
}

if includeSport {
  league.SportWrapper = league.Sport
} else {
  league.SportWrapper = league.SportId
}

c.JSON(http.StatusOK, league)

response to the api request:

{
  sport: {
    id: &lt;id&gt;,
    name: &lt;name&gt;
  }
}

it can also be:

{
  sport: &lt;sport_id&gt;
}

答案1

得分: 1

没有更多的上下文很难说。我假设SportId也是Sport结构的一部分。你基本上只返回ID或整个结构。

我建议只添加以下结构:

Sport Sport `json:"-"`

而不是直接将其序列化为json,你需要先创建一个映射(基本上是一个视图模型),其中设置了固定属性。在这里,你可以设置SportID或Sport结构,然后将映射序列化为json。

这也不是一个很好的解决方案,但优点是在你当前的解决方案中,响应代码的丑陋泄漏到数据模型中,进而泄漏到应用程序的其他部分。通过中间映射,丑陋的部分被限制在响应生成中。

// models/league.go
type League struct {
  Sport Sport
}

... 解析包含 ...

viewModel := map[string]interface{}

if includeSport {
  viewModel["sport"] = league.Sport
} else {
  viewModel["sport"] = league.Sport.ID
}

c.JSON(http.StatusOK, viewModel)
英文:

It's difficult to say without more context. I assume that the SportId is also part of the Sport struct. You are basically only returning the ID or the whole struct.

I would propose to only add the

Sport Sport `json:&quot;-&quot;`

struct. Instead of directly serializing it to json you would have to create a map (basically a viewmodel) first with the fixed attributes set. There you can set your SportID or Sport struct and after that you can serialize the map to json.

This is also not a very nice solution but the advantage is that in your current solution, the ugliness of your response code leaks into the data model and therefore into the rest of your application. With the intermediate map the ugly part is confined into the response generation.

// models/league.go
type League struct {
  Sport Sport
}

... parse includes ...

viewModel := map[string]interface{}

if includeSport {
  viewModel[&quot;sport&quot;] = league.Sport
} else {
  viewModel[&quot;sport&quot;] = league.Sport.ID
}

c.JSON(http.StatusOK, viewModel)

答案2

得分: 0

// models/league.go
type League struct {
SportID int64 json:"-" // 这里的json标签是用来指定在序列化和反序列化时的字段名
Sport Sport json:"-"
SportWrapper interface{} json:"sport"
}

var (
sportID int
sport Sport
)

// controllers/league.go
include := c.Request.URL.Query().Get("include")
if err := json.Unmarshal([]byte(include), &sportID); err != nil {
json.Unmarshal([]byte(include), &sport)
league.SportWrapper = sport
league.Sport = sport
} else {
league.SportWrapper = sportID
league.SportID = sportID
}

c.JSON(http.StatusOK, league)

如果你能将请求参数设置为 JSON 格式,你可以使用这段代码。

英文:
// models/league.go
type League struct {
	SportID int64 `json:&quot;-&quot;`
	Sport Sport `json:&quot;-&quot;`
	SportWrapper interface{} `json:&quot;sport&quot;`
}


var (
	sportID int
	sport Sport
)
// controllers/league.go
include := c.Request.URL.Query().Get(&quot;include&quot;)
if err := json.Unmarshal([]byte(unclude), &amp;sportID); err != nil {
	json.Unmarshal([]byte(unclude), &amp;sport)
	league.SportWrapper = sport
    league.Sport = sport
} else {
	league.SportWrapper = sportID
    league.SportID = sportID
}

c.JSON(http.StatusOK, league)

if you could set your request param to json you can use this.

huangapple
  • 本文由 发表于 2017年2月11日 17:29:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/42174308.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定