Based on this information, let's see if we can reframe things a bit. A few points:
- The purpose of encoding/decoding strategies is to allow consumer A of types B, C, and D to express that A knows better than B, C, and D how they should encode specific fields of data. The point here is that B, C, and D can have notions of how they want to encode, but A might actually know better based on their needs — say, B, C, and D are general types that encode
Dates and Data and don't particularly care in what format, but A knows that the API they are working with requires a different format
- This gives consumers of framework/library types a way to say "for my use case, I know that what I really need is a different format, in a way that you couldn't anticipate"
- These encoding/decoding strategies do not affect how synthesized implementations work — they simply override behavior for all fields of that type, whether or not they come from a synthesized implementation: a
Date is affected by the dateEncodingStrategy regardless of whether or not it was part of a synthesized implementation
- I take your comments about abandoning synthesis not as a misunderstanding of this point, but rather that if you want to enforce a specific format, you have to abandon synthesis in order to encode a
Date as a String/Int/Double/what-have-you manually, correct?
- I further take your language support comments to mean that we should have a way to express "don't really encode this as a
Date (which would follow the dateDecodingStrategy) but as some other underlying format that I specify, in a way that follows my spec", correct?
- If so, this is exactly the type of problem that adaptors would seek to solve — a per-property way of tapping in to encode/decode calls without having to give up full synthesis. I've discussed this in the past (and briefly in the linked thread above), but this isn't something that's been fully fleshed out because it would depend on other language features that aren't there yet
- The same complaint that you have about
Dates being such common, fundamental types is actually the reason we have these strategies in the first place — because JSON doesn't have a native format for dates, someone has to decide how to encode them; sometimes the best person to know how to do that is the person who wrote the type (you), and sometimes it is the person using the type (say, me). This will always be in conflict because there are situations in which either side can be "right" (and in the majority of cases, the person would be me, since I'm actually the one working with the API)
What I see being most in tension here is that this flexibility for consumers in the general case conflicts with your type's assertion that it knows how to encode itself. Some questions about that:
- Is your type domain-specific to working with only a single API spec?
- Is it conceivable that someone could want to use your type to encode in a format beside JSON, or use that type with their own API?
If the answer to both of these is "yes", then you can clearly see the conflict here: you can't both win out.
- If this type is domain-specific and intended to work with only one API, then I would say that it is incorrect for a consumer of your type to apply a
dateEncodingStrategy/dateDecodingStrategy willy-nilly, not just in the context of your type: their own types sent to that API will simply be wrong. That's not on you — that's on them. ¯\(ツ)/¯ There are many ways of incorrectly encoding, but if the API specifies a required date format, you as the author of types B, C, and D are not the one who needs to control that: it's consumer A who needs to ensure that, not just for your types, but for theirs as well. If they need to apply different formats in different places, they can use the .custom strategy to do that work; that's not something special about your type, but their responsibility regardless
- If this type is not domain-specific and can be encoded elsewhere, then I would not recommend you hard-code a format, since that defeats the flexibility that your consumers need to write the types out elsewhere
This is something that can be helped with language support in the explicit overriding case, but I recommend taking a look at how your types are intended to be used. Only you can tell us — what is the case here? Can you share the specifics for more context?
Completely separately, about your other issue:
Sorry, I was on the same page here, but perhaps poorly phrased my comment. The intent is to have the following:
public struct Foo : Codable {
public var name: String
public var something: MyThingWhichCantPubliclyBeCodable
}
yes? If something could conform to Codable, you'd have no problem, but it's because something can't that you don't get synthesis.
If I've understood you correctly, something can't conform to Codable because you don't want to publicly expose the init(from:), right? If so, this is also not Codable-specific; Swift doesn't allow for private/internal conformances of protocols on public types for good reasons.
This isn't specific to synthesis, either: you couldn't encode something manually regardless, since it doesn't conform to the protocol. All of the generic methods are written in terms of Encodable/Decodable conformance for a reason, and if you can't conform to the protocol, there isn't much you can do with the type.
Looks like you have your solution below, though: