JSON Mapping of Morphir tagged unions #135
Replies: 7 comments 9 replies
-
In order to properly weigh the pros and cons, we need the proposed alternate format. That will dictate what other tools would be able to work with it, which would give us a better perspective on the benefits. |
Beta Was this translation helpful? Give feedback.
-
So I've been thinking through alternatives and the con here and one possible alternative to both solutions above is the following: For the same structure as above: sealed trait Foo
case class Bar(meaningOfLife: Int, isThatRight: Bool) extends Foo
case class Baz(sayHi: String) extends Foo Encode it as the following JSON: ["Bar", {"meaningOfLife": 42, "isThatRight":true}]
["Baz", {"sayHi":"Hello"} A con of this approach is it leaves ambiguity when encoding certain classes of custom types in Elm. type Foo
= Bar Int Bool
| Baz String
| Biz { label: String, count: Int }
sample1 = Bar 42 True
sample2 = Baz "Hello"
sample3 = Biz {label = "widget", count = 99 } |
Beta Was this translation helpful? Give feedback.
-
I am also exploring to see if I can come up with a way to represent the Scala AST in a data structure/model which If there is success there, then I may push less on this initiative. |
Beta Was this translation helpful? Give feedback.
-
I think it would be important to make a decision here that we can stick to for the foreseeable future. The fundamental difficulty is that there is no standard solution for encoding tagged unions/inheritance in JSON. The de facto standardThe most frequently used approach is to map every constructor as a JSON object and use a discriminator field to identify which constructor it is. I laid this out in the original proposal: { "$type": "Bar", "meaningOfLife": 42, "isThatRight": true }
{ "$type": "Baz", "sayHi": "Hello" } One issue with this is that you need to pick a discriminator field name. Sometimes, especially in OOP languages, constructors are represented as subclasses so the JSON serialization tools tend to insert a So picking one standard field name that will work everywhere out-of-the-box is difficult. At the same time, most serialization libraries allow you to customize the field name so if we pick a name that doesn't align Another issue with this encoding is performance and modularity of serialization code. Since the discriminator field is part of the same object that contains the specific fields for each constructor as well, and fields don't have a guaranteed ordering, it's possible that you need to track back after finding the discriminator field to read the specific constructor fields. A better approachSome serialization libraries (ZIO JSON for example) decided to go with a serialization format that addresses both of the issues mentioned above: { "Bar": { "meaningOfLife": 42, "isThatRight": true } }
{ "Baz": { "sayHi": "Hello" } } There is no performance penalty here since there's always only one field at the top which is always the discriminator field. Also, we don't need to think about field names because the constructor name is the field name. From a purely technical perspective this is a clearly better approach, but most serialization libraries won't support it out-of-the-box. This is the fundamental dilemma. At least in my head. Any thoughts? |
Beta Was this translation helpful? Give feedback.
-
I like the approach. It gets my vote. |
Beta Was this translation helpful? Give feedback.
-
Yes, the better one :) |
Beta Was this translation helpful? Give feedback.
-
I am also in favor of "A better approach". |
Beta Was this translation helpful? Give feedback.
-
In one of our recent community calls there was a request to review how we map Morphir types to JSON. I'll lay out the current and proposed serialization and open it up for discussion.
Current Approach
Morphir defines a JSON mapping standard that we use consistently across all of our tooling (the IR, tests, backends, ...). Most of the mapping is straightforward and follows what most people would do if they manually mapped their types to JSON. The one exception is tagged unions (referred to as custom types in Elm, sum types in other contexts). Given the following sample type and values:
The JSON serialization would be:
It's simply an array where the first element is the name of the constructor (tag) the rest are the arguments.
Proposed Approach
The mapping above aligns nicely with most FP languages but a more frequently used representation of the same construct is through inheritance (I'll use Scala syntax for verbosity):
Which is usually mapped to JSON as:
This is much closer to what most people in mainstream languages would choose when mapping an "or" relationship to JSON.
Comparison
I'll attempt to collect pros and cons but please feel free to extend in the comments as I'm sure I'll miss some. Let's just list out pros and cons for the proposed solution to make things simpler. Just flip them to get the pros/cons for the current solution.
Pros
Cons
What do you think?
Beta Was this translation helpful? Give feedback.
All reactions