Unmarshalling dynamic JSON

polaris · · 433 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Hi! I want to parse a JSON string into some nested Structs, but atm I have a problem with dynamic values:</p> <pre><code>{ &#34;item1&#34;:{&#34;foo&#34;:[[&#34;name1&#34;, 1], [&#34;name2&#34;, 2]]}, &#34;item2&#34;:{&#34;foo&#34;:[{&#34;name&#34;:&#34;name3&#34;,&#34;amount&#34;:3}]} } </code></pre> <p>So in the JSON I have multiple items (item1, 2 ... N), which have the same values more or less. One difference is, that the value of foo is sometimes an array of arrays, sometimes an array of objects. In both cases they represent the same thing: a name and an amount. Is there a way to normalize this into a struct like:</p> <pre><code>type Foo struct { Name string Amount int } </code></pre> <p>I can parse one or the other format, but not both.</p> <hr/>**评论:**<br/><br/>connor4312: <pre><p>There are 3rd party JSON libraries that could make it a little easier, but if you want to stick with the standard library (arguably a better idea in most cases ;) ) try something like this: <a href="https://play.golang.org/p/TYZvuDB9qU">https://play.golang.org/p/TYZvuDB9qU</a> Foo implements the json.Unmarshaler interface, which lets you define custom behavior to use when decoding it.</p> <p>Note that the example lacks some error checking.</p></pre>DavsX: <pre><blockquote> <p>When you call json.Unmarshal it will then call your UnmarshalJSON method and if you decode logic is good you&#39;re done! Implementation: You could unmarshal the dynamic object into a map[string]interface{} which will be a combination of []string and map[string]string. Then just iterate over the map, type assert and extract the values appropriately.</p> </blockquote> <p>Thanks, I managed to get it working based on your example. Btw is handling dynamic data this &#34;difficult&#34; in all statically typed languages as in go? I generally have experience with dynamic typing only so far, where it just kinda works.. But then again no type safety. </p></pre>Redundancy_: <pre><p>The given JSON would be difficult to work with using Json Schema, RAML and other API specification tools. Even in dynamic languages, you&#39;d have to do an if-else on type that would be quite similar to unmarshalling into an interface{}, it&#39;s just more glaring in a strongly typed setup that guessing at the schema by looking at the content is not ideal.</p> <p>There are cases where it&#39;s valid design to switch the type of an attribute, like where you have a standardized message header (particularly in message buses with a Canonical Data Model and things like ReplyTo or CorrelationID fields). In those cases though, it&#39;s useful to have another field that determines the type of the content. Go supports that sort of use-case using json.RawMessage. </p></pre>joncalhoun: <pre><p>In my experience it can be this difficult in almost any static language, but it is typically a byproduct of the JSON being very poorly designed. Dynamic languages just tend to be more forgiving of poor design (eg using multiple formats to represent the same thing).</p></pre>parnmatt: <pre><p>Have all Structs that correspond to that particular field satisfy an interface. You then can have the surrounding Struct expect a implementation of that interface. </p> <p>You can then implement your own Unmarshal method using auxiliary Structs, such that you can then write specific implementations that Unmarshal each possible variation, and you keep the one that passes.</p> <p>I&#39;ve had to hack together something like that fairly recently. However, I&#39;m still fairly new to Go, there might be other, better approaches.</p></pre>saturn_vk: <pre><p>If you have another element in the json that tells you what kind of &#39;type&#39; foo is, you can defer the evaluation of foo for a later time, using json.RawMessage.</p></pre>Astrus: <pre><blockquote> <p>If you have another element in the json that tells you what kind of &#39;type&#39; foo is</p> </blockquote> <p>One hacky approach would be to simply try decoding it as an array, and if that fails, try decoding it as an object.</p></pre>tv64738: <pre><p>If there&#39;s no type field, the non-hacky way is via <a href="https://golang.org/pkg/encoding/json/#Decoder.Token" rel="nofollow">https://golang.org/pkg/encoding/json/#Decoder.Token</a> to see what shape the data is.</p> <p>If there is a type field, <a href="http://eagain.net/articles/go-dynamic-json/" rel="nofollow">http://eagain.net/articles/go-dynamic-json/</a></p></pre>Astrus: <pre><p>huh, TIL about <code>Decoder.Token</code>. Neat!</p></pre>xyproto: <pre><p>Is JPath any help, where you can specify a path to the value you are interested in?</p> <p><a href="https://github.com/xyproto/jpath" rel="nofollow">https://github.com/xyproto/jpath</a></p></pre>JackOhBlades: <pre><p>A custom UnmarshalJSON method on the struct sounds like the easiest way to go. </p> <p>When you call json.Unmarshal it will then call your UnmarshalJSON method and if you decode logic is good you&#39;re done! </p> <p>Implementation: You could unmarshal the dynamic object into a map[string]interface{} which will be a combination of []string and map[string]string. Then just iterate over the map, type assert and extract the values appropriately. </p> <pre><code>var m = map[string]interface{}{} err := json.Unmarshal(b, m) if err != nil { return err } for key, value := range m { switch value := value.(type) { case []string: //... case map[string]string: //.... } } </code></pre> <p>EDIT: I misread the json, here&#39;s another possible implementation.</p> <pre><code>var s = []interface{}{} err := json.Unmarshal(b, s) if err != nil { return err } for _, value := range s { switch value := value.(type) { case []string: //... case []map[string]string: //... } } </code></pre> <p>I hope this helps.</p></pre>plectid: <pre><p>Read: <a href="https://blog.gopheracademy.com/advent-2016/advanced-encoding-decoding/" rel="nofollow">https://blog.gopheracademy.com/advent-2016/advanced-encoding-decoding/</a></p></pre>

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

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