<p>Writing my second Go app here. Taking in JSON data from web, running some logic, and returning with JSON response. Simple enough.</p>
<p>I have a func that fills a struc with the http.Request data, successfully. What I'd like to do, is expand that function so that I can use it for a variety of structs, not just one strictly defined.</p>
<p>This code currently works, filling my "APIRequest" struct with data</p>
<pre><code>func GetAPIRequest(request *http.Request) APIRequest1 {
body, err := ioutil.ReadAll(io.LimitReader(request.Body, 1048576))
if err != nil {
panic("reading data from request body")
}
if err := request.Body.Close(); err != nil {
panic("closing body")
}
var object APIRequest1
if err := json.Unmarshal(body, &object); err != nil {
panic("converting data from request body into object")
}
return object
}
</code></pre>
<p>Now if I create a new struct, let's call it "APIRequest2", that has different variables, I have to make another function to full the object with data. I'd like to have just one function that fills any struct object with data from the json.Unmarshal</p>
<p>This code does not work:</p>
<pre><code>api_request1 := GetAPIRequest(request).(APIRequest1)
api_request2 := GetAPIRequest(request).(APIRequest2)
func GetAPIRequest(request *http.Request) interface{} {
body, err := ioutil.ReadAll(io.LimitReader(request.Body, 1048576))
if err != nil {
panic("reading data from request body")
}
if err := request.Body.Close(); err != nil {
panic("closing body")
}
var object interface{}
if err := json.Unmarshal(body, &object); err != nil {
panic("converting data from request body into object")
}
return object
}
</code></pre>
<hr/>**评论:**<br/><br/>danhardman: <pre><p>All my handler functions use this </p>
<pre><code>//Decode decodes a json body from a request
func Decode(r *http.Request, v interface{}) error {
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
return err
}
if obj, ok := v.(model.OK); ok {
return obj.OK()
}
return nil
}
</code></pre>
<p>So I create structs for all the requests I'm expecting and pass them to this function as the <code>v</code> argument. Is this what you're after?</p></pre>drvd: <pre><p>No you do not want "to have just one function that fills any struct object with data". You think you want but you don't.</p>
<p>What you want is: Write special code for struct 1. Then write special code for struct 2. Then you write special code for struct 3. Now the time has come for Atwood's law of three to kick in: You sit back, think about your code and maybe extract common subroutines or refactor to use common helpers, etc. pp.</p>
<p>Then you write code for struct 4 and struct 5. You want to think about your solution now a second time and rework it.</p>
<p>Generics are oversold.</p></pre>GarrettSYHampton: <pre><p>Bah... Really? Coming from C# here. I know generics are not part of Go, still getting used to the concept.</p>
<p>I know I can extract the body read logic, but am still stuck with creating a unique function, for every struct where just the object returned is changed. Seems like a lot of duplication and repetition.</p>
<p>This code below works, but the need for creating new func's for each struct feels wrong to me?</p>
<pre><code>api_request1 := GetAPIRequest1(request)
api_request2 := GetAPIRequest2(request)
func GetAPIRequest1(request *http.Request) APIRequest1 {
var APIRequest1
if err := json.Unmarshal(GetBodyData(request), &object); err != nil {
panic("converting data from request body into object")
}
return object
}
func GetAPIRequest2(request *http.Request) APIRequest2 {
var APIRequest2
if err := json.Unmarshal(GetBodyData(request), &object); err != nil {
panic("converting data from request body into object")
}
return object
}
func GetBodyData(request *http.Request) []byte {
body, err := ioutil.ReadAll(io.LimitReader(request.Body, 1048576))
if err != nil {
panic("reading data from request body")
}
if err := request.Body.Close(); err != nil {
panic("closing body")
}
return body
}
</code></pre></pre>peterbourgon: <pre><p>Presumably you have a couple of routes, like <code>/one</code> and <code>/two</code>, which correspond to APIRequest1 and APIRequest2 respectively.</p>
<p>So, this is how that would work:</p>
<pre><code>type APIRequest1 struct {
Foo int `json:"foo"`
Bar string `json:"bar"`
}
func handleOne(w http.ResponseWriter, r *http.Request) {
var req APIRequest1
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// do something with req
}
type APIRequest2 struct {
Baz []float32 `json:"baz"`
}
func handleTwo(w http.ResponseWriter, r *http.Request) {
var req APIRequest2
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// do something with req
}
func main() {
http.HandleFunc("/one", handleOne)
http.HandleFunc("/two", handleTwo)
log.Fatal(http.ListenAndServe(":8080", nil))
}
</code></pre>
<p>That's probably about as simple as it's going to get, for this contrived example. Is that not quite enough for you? What else would you expect?</p></pre>GarrettSYHampton: <pre><p>That's what I have now, everything works as expected which is great. </p>
<p>What I was hoping to end up with, is not having to write the json.Decoder line each time. Have a func I can call to serialize any struct from JSON.</p>
<p>Using your example, I would end up with something more along the lines of this: ("SerializeObject" is the func I am trying to build)</p>
<pre><code>func handleOne(w http.ResponseWriter, r *http.Request) {
req APIRequest2 := SerializeObject(GetBodyData(r)).(APIRequest1)
// do something with req
}
func handleTwo(w http.ResponseWriter, r *http.Request) {
req APIRequest2 := SerializeObject(GetBodyData(r)).(APIRequest2)
// do something with req
}
func GetBodyData(request *http.Request) []byte {
Attempt to read in data from the request body
body, err := ioutil.ReadAll(io.LimitReader(request.Body, 1048576))
if err != nil {
LogError("reading data from request body", err)
}
if err := request.Body.Close(); err != nil {
LogError("closing body", err)
}
return body
}
func SerializeObject(json_data []byte) interface{} {
var object interface{}
if err := json.Unmarshal(json_data, &object); err != nil {
LogError("converting data from request body into object", err)
}
return object
}
</code></pre></pre>peterbourgon: <pre><p>But your version is longer than mine, and you effectively ignore the error you get if the JSON decode fails, so it's actually less safe, too. In fact, you're effectively ignoring all of your errors—never a good idea.</p>
<p>Why do you want to avoid json.Decode? It's functionally no different than typing SerializeObject, right?</p>
<p>More generally, Go doesn't support the kind of generic programming you're trying to do here. You can't just unmarshal JSON data into an arbitrary "object" and then type-assert it to something concrete. Rather, you have to take the concrete object, and populate it via a JSON unmarshaler. And in general, and especially when you're this new with the language, any use of the empty interface <code>interface{}</code> in your function parameters or return values is pretty much guaranteed to be a sign you're doing something the wrong way.</p>
<p>Other nits:</p>
<ul>
<li>Go names never use underscores</li>
<li>Why are you using a LimitReader with that magic number?</li>
<li>It's not SerializeObject, rather DeserializeObject</li>
</ul></pre>calebdoxsey: <pre><p>You have to give Go a type to populate, which means typically rather than returning something with <code>func GetX() X</code> you do: <code>func Get(dst interface{})</code> and use reflection or a type switch. (In this case json does all this for you)</p>
<p>For example you could do this:</p>
<pre><code>package main
import (
"encoding/json"
"io"
"net/http"
)
const MaxBodySize = 1048576
func unmarshal(req *http.Request, dst interface{}) error {
defer req.Body.Close()
return json.NewDecoder(io.LimitReader(req.Body, MaxBodySize)).Decode(dst)
}
func handle(route string, handler func(http.ResponseWriter, *http.Request) interface{}) {
http.HandleFunc(route, func(res http.ResponseWriter, req *http.Request) {
obj := handler(res, req)
if err, ok := obj.(error); ok {
http.Error(res, err.Error(), 500)
return
}
json.NewEncoder(res).Encode(obj)
})
}
func main() {
handle("/example", func(res http.ResponseWriter, req *http.Request) interface{} {
var apiRequest struct {
X, Y int
}
err := unmarshal(req, &apiRequest)
if err != nil {
return err
}
// do stuff
return "whatever you want"
})
http.ListenAndServe(":8080", nil)
}
</code></pre></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传