That seems the dual problem, that is, communicating intent with a data structure (instead of modeling data): I agree that an optional in itself doesn't cut for this use case, but neither a completely general unknown case. In the example you're referring to I'd use a generic type that better conveys the intent, like (this is something I actually use in production code):
enum Update<A> {
case unchanged
case set(A)
}
In your specific example, A would be an Optional. The fact that those JSON APIs infer a particular meaning the structure of the JSON object is an implementation detail of the APIs themselves: JSON is just a particular serialization strategy and, in the case of those APIs, the Update value would be translated accordingly.
A comment in the thread says:
In future codegen we'll be working with a custom enum that makes this clearer, but for what we've got now, the double-optional is the best way to represent it.
That's the point. For representing that use case (a dual case of domain modeling) the best solution is a custom enum that suits that domain-specific logic.
The distinction here is in how you model a domain entity vs how you model the server output data: I agree that the latter could be anything, and if you (like everyone, really) use JSON, you unfortunately must the pay the price of the limited power of JSON to model any complex data structure.
For example, drawing from your example:
struct Book {
var name: String
var category: Category
enum Category {
case physical(thickness: Thickness)
case digital(fileSize: Int)
}
}
This is, to me, the correct way to model such domain entity. But when getting a book from the server, you'll likely going to have a flat object with optional fields, and maybe a field that represents which case of the enum you're dealing with, for example:
{
"name": String,
"category": physical|digital
"thickness": Number?
"fileSize": Number?
}
To represent this with a Swift type, you could use something like the following:
struct RawBook: Decodable {
var name: String
var category: String
var thickness: Double?
var fileSize: Int?
}
This is just a raw representation, and because it models a JSON, it's going to be a flat data structure with optionals. When decoding this, the fact that, for example, thickness is null or is absent is irrelevant.
You would then have, maybe, an initializer on Book that takes a RawBook, like the following:
extension Book {
init(raw: RawBook) throws {
/// Here you can `switch` on `raw.category`, and `throw` if the category is unrecognized or the non-null properties don't match their category, or maybe use a "sensible" default
}
}
In this case, a "3-way optional" wouldn't be useful because, for the domain entity it makes no sense, and for the raw representation (that again is a consequence of how JSON works) it doesn't matter.
Definitely agree: the presented examples, up to this point, don't suggest, to me, the need for a standard library type.