In the general case, Codable
doesn't guarantee the semantics you're looking for, so the ComesFromAPI
code you show isn't guaranteed to be safe; however, in a constrained scenario (using this wrapper to only encode and decode types that you control, and only using JSONEncoder
/JSONDecoder
), this should be okay.
The implementation of encode(to:)
here has two main issues:
- Encoding two objects at the same "level" and expecting them to interleave in the way that
JSONEncoder
allows isn't guaranteed. In general,Codable
doesn't guarantee in general that you can callencode(to:)
more than once at a given object hierarchy depth at all (e.g., anEncoder
that would like to write output in a streaming fashion would be within its rights to set a precondition that this isn't allowed, since it wouldn't be able to interleave the contents of the objects)- There's also the matter that
Codable
requires that bothmetadata
andbody
request the same encoding container within theirencode(to:)
implementations; otherwise it is a hard error, and you're likely to crash - A bit more detail in Aggregating two Encodable's
- There's also the matter that
- Calling
encode(to:)
directly on an object and giving it anEncoder
prevents the encoder from handling that object in any sort of special way. This isn't likely to be an issue if the object is of a type you control that isn't otherwise special-cased by the encoder (e.g.Date
,URL
,Dictionary
, and several other types are handled specifically byJSONEncoder
/JSONDecoder
, and callingencode(to:)
directly on them will yield different results than encoding them "properly" through a container)
Based on the detail you've given, it doesn't sound like either of these are concerns: it doesn't sound like you're planning on writing to a format other than JSON, and it doesn't outwardly seem like you'll be encoding or decoding anything other than ComesFromAPI<some type you control already>
. That being said, I don't know if I would personally feel comfortable relying on this — it's extremely unlikely that the behavior of JSONEncoder
/JSONDecoder
could change to suddenly make this illegal, but as @tera says, for some, there's something to be said for writing it so simply that anyone could understand the behavior (and that it can't possibly break).
If Swift had support for hygienic macros or mixins natively in the language, I'd say that decorating your types in a way that helped automate "option 2" above would likely be my go-to approach, but in the absence of that, one alternative to writing it all out by hand would be using a source generator to do the heavy lifting.