Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Discussion] Remove type field from union types #111

Open
raiju opened this issue Oct 1, 2018 · 15 comments
Open

[Discussion] Remove type field from union types #111

raiju opened this issue Oct 1, 2018 · 15 comments

Comments

@raiju
Copy link
Contributor

raiju commented Oct 1, 2018

Conjure's union types can end up being extremely inefficient in terms of bytes in the wire format, for example when encoding ASTs (where there is generally a lot of polymorphic branching). The double-encoding of the type for union types is the main first order cause, as for small-ish ASTs it will almost double the size:

Lit(Int(42)):
{"type":"lit","lit":{"type":"int","int":42}}
vs
{"lit":{"int":42}}

while not actually adding any new information. @markelliot believes the second form can be implemented for all currently-known targets.

Has this already been discussed? Is this worth the extra work to change?

@markelliot We discussed this a little bit ago, in the context of the Conjure encoding overhead (in terms of bytes used). As it will be significantly more expensive to make this change once this specification has been released (as it's a wire format change), I wanted to start a discussion now.

@raiju
Copy link
Contributor Author

raiju commented Oct 1, 2018

Actions:

  • Check which implementations require the type field

Moving forward:

  • union2 or a deprecation "concise" flag (when all deserialization can decode concise form)
  • Mime-type? Header containing conjure version?

@sfackler
Copy link
Member

sfackler commented Oct 1, 2018

The proposed layout is pretty trivial to support on the Rust side of things as well.

@markelliot
Copy link
Contributor

On the Java end: I think we can revisit the unions-as-objects-with-optionals PR I put up a little while back, it didn't need the type field and generated code had fewer bonus classes.

@nmiyake
Copy link
Contributor

nmiyake commented Oct 3, 2018

There is actually a case that is a bit difficult to handle without the type field (for sure in Go, but I believe this will also apply for Java and any other implementation that doesn't parse the raw JSON directly) -- a union type where the value is an optional which is absent.

Consider the union:

MyUnion:
  union:
    foo: optional<boolean>
    bar: optional<string>

If we have an instance whose content is bar with Optional.empty(), then the current serialized form is:

{"type":"bar","bar":null}

If we remove the type key, then the serialized form is:

{"bar":null}

If we just unmarshal this into a union object, in many languages it won't be possible to distinguish the type -- for example, if you unmarshal it into a Java file with 2 fields (Optional<Boolean> and OptionalInt), both values will just be null/absent. This is the same for Go. The existence of the type field does make handling thins case easier.

You can obviously get around this by inspecting the actual JSON (look for the presence/absence of the specific key), but that will probably be more overhead/work in the different languages. The same is true for handling the "unknown" case.

This isn't a hard blocker, but wanted to note it since this does make things more complicated than my initial assessment.

@raiju
Copy link
Contributor Author

raiju commented Oct 8, 2018

That's a good point, I hadn't thought of that case.

In Typescript, absent & null are different values (undefined/null), so that should just work. I think it's possible in Java/Python as null values end up with a null/None map entry (as opposed to no entry), but would have to double-check. Honestly not sure about Go & Rust though.

@sfackler
Copy link
Member

sfackler commented Oct 8, 2018

It's not a problem in Rust - we have very low level access to the JSON structure so a null value is distinguishable from a missing one.

@maciekf
Copy link

maciekf commented Nov 2, 2018

+1 from Flows.

We have a DSL which allows users to define their workflows and we use unions in a few places (workflow state parameter types, expressions using the aforementioned parameters). The produced JSON/YAML is much bigger than necessary which makes it harder to read or edit.

@ericanderson
Copy link
Member

Why not do something more like { __type: string, ...typesProperties }?

@sfackler
Copy link
Member

sfackler commented Feb 7, 2019

That either requires us to guarantee that the __type field comes first in the serialized form, which can be hard to enforce through various layers of glue, or buffer all of the properties until we figure out what type to deserialize into.

@carterkozak
Copy link
Contributor

That would not work for primitive types, not everything has properties.

@markelliot
Copy link
Contributor

All unions encode the value in a specific field name, and the field name can be used to identify the type of its value (defitionally).

One might model union types as a shorthand for a single type with many optional fields and a guarantee that exactly one of those fields is non-empty (and thus present in the wire format).

@tom-s-powell
Copy link

Has there been any further discussion on this topic? Have a use-case where users will be constructing JSON queries that heavily involve union types and the non-type proposal would be far less verbose. Could something like this be a feature flag that a service can configure based on what best fits the use-case and intended client?

@markelliot
Copy link
Contributor

@tom-s-powell I think we'd need to do some work to ensure that all deserializers across all the languages get to the point where type is ignored and then do a major rev on the protocol in order to stop producing the extra key. To my knowledge, none of the languages have the requisite changes in to use the field key as a differentiator for type instead of examining the type key..

sfackler added a commit to palantir/conjure-rust that referenced this issue Aug 19, 2019
@sfackler
Copy link
Member

Rust implementation: palantir/conjure-rust@181e0a6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants