Need some suggestions, opinions, etc

polaris · · 586 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<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&#39;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 &#34;APIRequest&#34; 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(&#34;reading data from request body&#34;) } if err := request.Body.Close(); err != nil { panic(&#34;closing body&#34;) } var object APIRequest1 if err := json.Unmarshal(body, &amp;object); err != nil { panic(&#34;converting data from request body into object&#34;) } return object } </code></pre> <p>Now if I create a new struct, let&#39;s call it &#34;APIRequest2&#34;, that has different variables, I have to make another function to full the object with data. I&#39;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(&#34;reading data from request body&#34;) } if err := request.Body.Close(); err != nil { panic(&#34;closing body&#34;) } var object interface{} if err := json.Unmarshal(body, &amp;object); err != nil { panic(&#34;converting data from request body into object&#34;) } 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&#39;m expecting and pass them to this function as the <code>v</code> argument. Is this what you&#39;re after?</p></pre>drvd: <pre><p>No you do not want &#34;to have just one function that fills any struct object with data&#34;. You think you want but you don&#39;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&#39;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&#39;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), &amp;object); err != nil { panic(&#34;converting data from request body into object&#34;) } return object } func GetAPIRequest2(request *http.Request) APIRequest2 { var APIRequest2 if err := json.Unmarshal(GetBodyData(request), &amp;object); err != nil { panic(&#34;converting data from request body into object&#34;) } return object } func GetBodyData(request *http.Request) []byte { body, err := ioutil.ReadAll(io.LimitReader(request.Body, 1048576)) if err != nil { panic(&#34;reading data from request body&#34;) } if err := request.Body.Close(); err != nil { panic(&#34;closing body&#34;) } 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:&#34;foo&#34;` Bar string `json:&#34;bar&#34;` } func handleOne(w http.ResponseWriter, r *http.Request) { var req APIRequest1 if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // do something with req } type APIRequest2 struct { Baz []float32 `json:&#34;baz&#34;` } func handleTwo(w http.ResponseWriter, r *http.Request) { var req APIRequest2 if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // do something with req } func main() { http.HandleFunc(&#34;/one&#34;, handleOne) http.HandleFunc(&#34;/two&#34;, handleTwo) log.Fatal(http.ListenAndServe(&#34;:8080&#34;, nil)) } </code></pre> <p>That&#39;s probably about as simple as it&#39;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&#39;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: (&#34;SerializeObject&#34; 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(&#34;reading data from request body&#34;, err) } if err := request.Body.Close(); err != nil { LogError(&#34;closing body&#34;, err) } return body } func SerializeObject(json_data []byte) interface{} { var object interface{} if err := json.Unmarshal(json_data, &amp;object); err != nil { LogError(&#34;converting data from request body into object&#34;, 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&#39;s actually less safe, too. In fact, you&#39;re effectively ignoring all of your errors—never a good idea.</p> <p>Why do you want to avoid json.Decode? It&#39;s functionally no different than typing SerializeObject, right?</p> <p>More generally, Go doesn&#39;t support the kind of generic programming you&#39;re trying to do here. You can&#39;t just unmarshal JSON data into an arbitrary &#34;object&#34; 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&#39;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&#39;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&#39;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 ( &#34;encoding/json&#34; &#34;io&#34; &#34;net/http&#34; ) 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(&#34;/example&#34;, func(res http.ResponseWriter, req *http.Request) interface{} { var apiRequest struct { X, Y int } err := unmarshal(req, &amp;apiRequest) if err != nil { return err } // do stuff return &#34;whatever you want&#34; }) http.ListenAndServe(&#34;:8080&#34;, nil) } </code></pre></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

586 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传