You Are Doing JSON APIs Wrong (“foo”:{}, not “type”: “foo”)
gist.github.com
gist.github.com
If you render this `{"MyMessageType":{...}}` structure, people who live in statically typed languages will hate you. At best you've created a union type with a truly vast number of possible cases. In practice you've forced this to be read as `Map<String, JsonNode>` and dynamically interpreted.
Unless your problem domain routinely streams gigabytes of data across an API, don't do this. And if it does stream gigabytes of data across an API, figure out how to chunk the data and still don't do this.
Like what even is the return type for parseJSONBlobToOneOfFiftyMessageTypes(String json)? Guess you could return a generic APIMessage and a type object so the caller can cast it but now you're back to playing dynamic games.
[1] https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...
function f(x: A | B | C) {
if ('A' in x) {
// x is narrowed to A
} else {
// x is narrowed to B | C
}
}The problem is that by the time you stream gigabytes of data, if you have done it the wrong way, you now have to add a new API revision for the right format, update your whole ecosystem - if the protocol is even in your control. If you did (or specced) it right from the start, you'd only have to adjust the clients at that point, and it would have been just as easy up until then.
GET /assets/123
{
"Asset": {
"owner": {
"Person": {
"name": "Bob",
"contact": {
"Address": {
"street1": "blah",
...
}
}
}
}
}
}
On the other hand, the traditional approach: GET /assets/123
{
"owner": {
"type": "Person",
"name": "Bob",
"contact": {
"type": "Address",
"street1": "blah",
...
}
}
}
}
This is much more sane to navigate. And when you end up making some previously-static node polymorphic (as happens), the structure does not change.I honestly have a hard time believing that you prefer:
asset.Asset.owner.Person.contact.Address.street1
to: asset.owner.contact.street1
If so, I think you should really announce this first and foremost in your article so that everyone can take a proper measure of your sanity :-)IME, we have one, at most two polymorphic fields per message. And for an example chosen deliberately to be annoying, I still like the deeper tree better- at least it advertises that something unusual is going on.
#[derive(Debug,serde::Deserialize)]
struct MyMessage {
data: i32,
}
#[derive(Debug,serde::Deserialize)]
enum Example {
MyMessageType(MyMessage)
}
fn main() {
dbg!(serde_json::from_str::<Example>(r#"{"MyMessageType":{"data":5}}"#));
}
Running: [src/main.rs:12] serde_json::from_str::<Example>(r#"{"MyMessageType":{"data":5}}"#) = Ok(
MyMessageType(
MyMessage {
data: 5,
},
),
)
https://play.rust-lang.org/?version=stable&mode=debug&editio...I'm not sure why you are forced to read it into a Map<String, JsonNode> unless you are doing your parsing without type information in which case any solution is going to require a map.
2. If you're sending me gigabytes of JSON, something has gone horribly wrong. Split it up, or send me the result in a more suitable format.
Gigabytes of XML, coming right up!
1. There's many ways for the compiler to narrow a union type [0]
2. There's a better solution than depending on "easy" narrowing for excessively large union types: type guards, callback registry, etc.
[0]: https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgA...
type Message { contentA?: ContentAStructure, contentB?: ContentBStructure, }
on read, you are already accessing the property data as typed right?
There was a thing called Reverse Hungarian that put an abbreviation for the type in the variable name. That had some value to work on very small screens without much scrolling.
Fields of variant/oneof/union type are really useful, so I don't agree with this. The problem regarding JSON serialization of type-tagged fields and streaming parsers is very well observed though.
I suppose one nice thing about adding a sibling "type" key, is it allows you to have the option to make a non-variant field a variant later on (by allowing the type field to have a default if it's missing).
{ "type": "a", "version": "1", "payloadJson": "{\"a\": 10}" }
What's wrong with REST and leaving stuff like versioning and resource type to HATEOAS?
I've also seen this before, but I can't say I didn't laugh:
HTTP/1.1 200 Ok
Content-type: application/json
{"status": 404, "type": "xml", "body": "<entry><name>...</name><author>...</author></entry>"}