My two cents. I tend to agree me too that it's bad...
# arrow
a
My two cents. I tend to agree me too that it's bad design.
null
should not be used as a valid value IMO (not in the api response, not in json, not in code). null doesn't convey lot of information. if null is used to express something, chances are there are more explicit ways to encode that information semantically and in a more type-safe way (if option alone is not enough to express all the possible scenarios), instead of appending a meaning to the null value (for instance, ADTs etc). Close to the DB layer (in the code) it might make sense to store raw null values, but in between a db and the api response i would expect a number of mapping layers and business logic where relying on a specific meaning of null values is not necessary and maybe (IMO) not even advisable.
r
If
null
is not a valid value, then how will be if on registration form, user can set his birthday, but this is not mandatory, what value should it has in json?
t
literally nothing. Just send the json without that field.
if the data is not there, simply don't send it. If there is some meaning to a null, use something explicit instead of null.
r
so, we have a nullable column in database "birthday", so we have 3 cases, 1. send a date 2. send null, so user want to remove the date 3. send nothing, how will be? we can not modify the data, but how in code we will know that user sent nothing? we can not use
null
, it has some meaning already
t
I see, using rest: Either using PUT instead of patch (simplifies every single thing, specially if your BE and FE are separated codebases), or having that field be an optional instead of plain value, or having "birthday" being a subresource, so you can just DELETE, or using RPC instead of REST. I guess for some profile service rest is better.
r
so, or you do a PUT(update entire resource), or we should have an endpoint for each property, do you think this simply the API?
p
@rrader you are modeling your request such as the received data has to be the final value. If instead you modelled your data as a set of transformations to apply to your model you’d avoid that issue.
i.e. a different message for updating to empty and removing
{ “op”: “delete”, “value”: “ohno” } { “op”: “update”, “value”: null }
first one ignores value, second one sets it to null
you can add validation at the edges of the layer, i.e. updates cannot be null, just empty string
the point is using sealed operations over the network rather than ad-hoc semantics based off convention
1
r
Well, it is an approach, but it is not REST, and for me this looks ugly, this also is not supported by OpenAPI/Swagger, so I have to change all my tools
p
it isn’t rest to have these shared semantics between missing field and null field, which is the cause of your pain
rest, same as agile, has been overloaded to mean whatever’s convenient for some stakeholders
r
if it is not REST, than what is PUT & PATCH in REST?
p
I’ve had rest services where everything was POST, so I don’t really know what true rest is really
r
in REST you work with resources, in your approach you work with operations
p
your problem is not as uncommon as to not have been solved by a rest service before
yes but rest doesn’t tell you whether a null on a json field is the same as a missing field
r
I solved it in JavaScript/TypeScript, there you have 2 values
null
and
undefined
p
and Java isn’t dynamic enough to know either, so you have to fall back to custom de/serializer to add semantics
r
why? you have types, you have
Optional
and you can have
Undefined
that's it
p
I’m aware, on my day to day I work on Flow (as in, developing flow itself), and null/undefined desambiguation is still an issue decades later for anyone that’s not a full-time JS developer
closed objects is a good solution around it, but safety blows out of the window with these APIs where undefined and null have different semantics ;_____;
r
yes, because they are not types, just like
null
in java, but it is solved by a class
Optional
p
Optional<A?> is a solution
you still need a custom deserializer
and that’s what gives you the semantics
you could implement it the other way around, where None means you received a null, and a Some(null) means you didn’t receive a field 😄
you shouldn’t, but you could
the point is that you’re not giving the semantics yourself
and at that point you could just do
Copy code
sealed class OptionalValue<A> { 
  inline class Available(val value: A) 

  object Missing: Value<Nothing>

  object Disabled: Value<Nothing>
}
and be explicit about it
r
my point is to have to classes
Optional
&
Undefined
that are explicit
p
what do you think about the sealed class above?
so you now have different semantics for missing field, disabled field, and “zero” values like
0
and
""
without the need for convention
r
it is also a solution, but is harder to generate documentation from it
p
KDoc
this is on your client
not on your swagger
r
I don't get it, I just propose to add a class, same as you, why my solution is not good?
p
Undefined could work as a typealias for Option, you’re right
my concern is being explicit
I prefer to document using types rather than comments
r
in my case 1. Undefined<String> - optional & non-nullable 2. Undefined<String?> - optional & nullable 3. String - required & non-nullable 4. String? - required & nullable
p
if I receive an OptionalValue the compiler forces me to handle the case where a value comes as
null
even when it’s not supposed to
r
that's it
yes it is documented by types
is it easy to generate swagger from it
p
yeah, and you enforce it as deserialization
else the data is malformed
and you throw or return an either or an option or something
my solution was for when you don’t enforce anything at deserialization
which may be overkill
😛
r
if you do not enforce that, than is hard to generate a correct swagger from it
BTW, this will also help in multi-platform for JS
p
JS is enforcing you to use dynamic types so they can just spread network results on existing objects
so they’re not really helping you be multi-platform at all
r
but interoperability? how will be if from js will be sent
undefined
value?
p
if you send a sealed type it’ll work the same as TS’s enum types (IIRC), or any other runtime desambiguation that’s done in Redux on vanilla JS
it’s not rest
you’re right, so no swagger etc
so you’re stuck with JS-first endpoints 😄
r
🙂
p
now it’s when I shill for GraphQL, but I’m on holidays hahaha
r