TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Decoding JSON sum types in Go without panicking

60 pointsby misonic2 months ago

9 comments

shabbyrobe2 months ago
IME this is a longstanding pain point with Go. There&#x27;s an attempt to propose an encoding&#x2F;json&#x2F;v2 package [1] being kicked around at the moment [2], spawned from a discussion [3].<p>This at least seems to improve the situation of marshalling to&#x2F;from an interface directly slightly by providing the ability to pass custom Unmarshalers for a specific type (via json.WithUnmarshalers and json.UnmarshalFunc) to the Unmarshal functions, but it appears to still have the inefficient double-decode problem. Or I just haven&#x27;t found a decent way around it yet.<p>Looks like they&#x27;re intentionally punting on a first class solution until (if) the language gets some sort of sum type, but I still think the second-class solution could do a bit more to make this extremely common use-case more convenient. Pretty much every serious production Go app I&#x27;ve worked on in the last 10 years or so has had some horrible coping strategy for the &quot;map a field-discriminated object to&#x2F;from implementations of an interface&quot; gap, often involving some sort of double-unmarshal.<p>Quote from the proposal [1]:<p>&gt; <i>First-class support for union types: It is common for the type of a particular JSON value to be dynamically changed based on context. This is difficult to support in Go as the equivalent of a dynamic value is a Go interface. When unmarshaling, there is no way in Go reflection to enumerate the set of possible Go types that can be stored in a Go interface in order to choose the right type to automatically unmarshal a dynamic JSON value. Support for such use cases is deferred until better Go language support exists.</i><p><pre><code> [1]: https:&#x2F;&#x2F;pkg.go.dev&#x2F;github.com&#x2F;go-json-experiment&#x2F;json [2]: https:&#x2F;&#x2F;github.com&#x2F;golang&#x2F;go&#x2F;issues&#x2F;71497 [3]: https:&#x2F;&#x2F;github.com&#x2F;golang&#x2F;go&#x2F;discussions&#x2F;63397</code></pre>
评论 #43418959 未加载
评论 #43417887 未加载
评论 #43418386 未加载
评论 #43418993 未加载
reactordev2 months ago
Cool but all you really needed to do was fix the contract between NewActionDeleteObject’s struct creation and the switch statements result = print. What’s really crazy is you can create anonymous structs to unmarshal json without having to fully flesh out data models thanks to its “we’ll only map the fields we see in the struct” approach. Mapstructure takes this even further with squash. In the end, the type checker missed the error because it was user error by contract and that, due to lack of validation that the actions were constructed properly, resulted in a rabbit hole debugging session that ended with an excellent blog post about the gotchas of unmarshaling.
评论 #43417690 未加载
nasretdinov2 months ago
BTW double unmarshalling (and double marshalling) can be quite slow, so to speed up determining the object type you can extract the type field e.g. by using gjson (<a href="https:&#x2F;&#x2F;github.com&#x2F;tidwall&#x2F;gjson" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;tidwall&#x2F;gjson</a>). It can be easily 10x faster for this kind of scenario
mccanneabout 2 months ago
Nice article!<p>Decoding sum types into Go interface values is obviously tricky stuff, but it gets even harder when you have recursive data structures as in an abstract syntax tree (AST). The article doesn&#x27;t address this. Since there wasn&#x27;t anything out there to do this, we built a little package called &quot;unpack&quot; as part of the SuperDB project.<p>The package is here...<p><a href="https:&#x2F;&#x2F;github.com&#x2F;brimdata&#x2F;super&#x2F;blob&#x2F;main&#x2F;pkg&#x2F;unpack&#x2F;reflector.go" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;brimdata&#x2F;super&#x2F;blob&#x2F;main&#x2F;pkg&#x2F;unpack&#x2F;refle...</a><p>and an example use in SuperDB is here...<p><a href="https:&#x2F;&#x2F;github.com&#x2F;brimdata&#x2F;super&#x2F;blob&#x2F;main&#x2F;compiler&#x2F;ast&#x2F;unpack.go" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;brimdata&#x2F;super&#x2F;blob&#x2F;main&#x2F;compiler&#x2F;ast&#x2F;unp...</a><p>Sorry it&#x27;s not very well documented, but once we got it working, we found the approach quite powerful and easy.
评论 #43424471 未加载
the_gipsyabout 2 months ago
Shameless plug: I wrote a &quot;JSON Tagged Union&quot; package for go: <a href="https:&#x2F;&#x2F;github.com&#x2F;benjajaja&#x2F;jtug" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;benjajaja&#x2F;jtug</a><p>Let&#x27;s you decode something like<p><pre><code> [ { &quot;type&quot;: &quot;A&quot;, &quot;count&quot;: 10 }, { &quot;type&quot;: &quot;B&quot;, &quot;data&quot;: &quot;hello&quot; } ] </code></pre> into some go like:<p><pre><code> type Tag string const ( A Tag = &quot;A&quot; B Tag = &quot;B&quot; ) type StructA struct { Type Tag `json:&quot;type&quot;` Count int `json:&quot;count&quot;` } type StructB struct { Type Tag `json:&quot;type&quot;` Data string `json:&quot;data&quot;` } </code></pre> by writing a (subjectively) minimal amount of boilerplate:<p><pre><code> type Union = jtug.Union[Tag] type List = jtug.UnionList[Tag, Mapper] type Mapper struct{} func (Mapper) Unmarshal(b []byte, t Tag) (jtug.Union[Tag], error) { switch t { case A: var value StructA return value, json.Unmarshal(b, &amp;value) case B: var value StructB return value, json.Unmarshal(b, &amp;value) default: return nil, fmt.Errorf(&quot;unknown tag: \&quot;%s\&quot;&quot;, t) } } </code></pre> This shows that now it&#x27;s possible to use `json.Unmarshal` directly:<p><pre><code> var list List err := json.Unmarshal([]byte(`[ {&quot;type&quot;:&quot;A&quot;,&quot;count&quot;:10}, {&quot;type&quot;:&quot;B&quot;,&quot;data&quot;:&quot;hello&quot;} ]`), &amp;list) for i := range list { switch t := list[i].(type) { case StructA: println(t.Count) case StructB: println(t.Data) &#x2F;&#x2F; etc. } } </code></pre> Of course, it relies on reflection, and is generally not very efficient. If you control the API, and it&#x27;s going to be consumed by go, then I would just not do tagged unions.
akpa1about 2 months ago
Interesting to see V mentioned here. Is it still the chaotic mess of a language that&#x27;ll never be like it was a few years ago?
byrnedo2 months ago
Pulled my hair out about doing this all over the place when integrating with a node api, ended up writing <a href="https:&#x2F;&#x2F;github.com&#x2F;byrnedo&#x2F;pjson" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;byrnedo&#x2F;pjson</a>. Feels like this should be covered as a more first class thing in go.
评论 #43419317 未加载
foofoo4uabout 2 months ago
Surprising to see V lang brought up. What’s it current reputation?
hesus_ruiz2 months ago
Reading the article I got the same conclusion as every time I approach sum types: they are ONLY useful for addressing malformed JSON structs of hacking BAD data structure&#x2F;logic design, at least for most business applications (for system-level programs my reasoning is different).<p>The example JSON in the article, even if it may be common, is broken and I would not accept such design, because an action on an object must require the action AND the object.<p>For many year, I have advised companies developing business applications to avoid programming constructs (like sum types) which are very far from what a business man would understand (think of a business form in paper for the first example in the article). And the results are good, making the business logic in the program as similar as possible to the business logic in terms of business people.
评论 #43418180 未加载
评论 #43418997 未加载
评论 #43423174 未加载
评论 #43419315 未加载
评论 #43418609 未加载
评论 #43419102 未加载