Codable - get source code for automatically generated init(from:) and encode(to:) methods

I have many large structs in my library which need to be Codable. The majority of the properties can be decoded and encoded like they would be in the automatically synthesized init(from:) and encode(to:) methods. However, there are a few properties that require special handling. Does anyone know how I can retrieve the source code for the init(from:) and encode(to:) methods that Swift automatically synthesizes so that I can just make changes in the places I need to without having to type out everything manually?

1 Like

You can try to use property wrappers to customize serialization of certain fields.

1 Like

IIRC this has come up in the past and everyone thinks it's a good idea, but it's never been high-enough priority to get done.

If it's a very few properties in a very large struct, it may be feasible isolate the special cases to computed properties, with parallel private properties that exist only for Codable, but that isn't a great solution either.

1 Like

I’m not an expert on this, but it was my understanding that if you just write the custom init(from:) and encode(to:) for the types of the members that don’t work with the implicit ones, then you can still use the implicit one for the main struct.

Isolating the special cases to computed properties would mean that they would have to be recomputed every time that they're accessed. And making them lazy properties would mean that I would have to store the instances in mutable variables. I don't want to have to do any of these things.

It's not the types of the properties that require custom decoding/encoding, it's the specific properties that require custom decoding/encoding, which may all be of the same type.

What does IIRC stand for?

That means “if I remember correctly.”

I just checked Apple’s documentation and my recollection is accurate. If the types of your type’s properties all conform to Codable then its conformance is synthesized just by adding Codable to its inheritance list.

The same principle applies to custom types made up of other custom types that are codable. As long as all of its properties are Codable , any custom type can also be Codable .

I believe Peter's question is about the properties' values, not types.

For example if you have a key-value pair "foo": "bar" in a large JSON object that you want to deserialise as foo: String = "barzbarz", although String itself is Codable, you still need to transform the value during deserialisation. If foo resides in a very large struct whose all other properties do not need any special treatment in deserialisation, then it would be nice to have a way to specify the special treatment for foo and leave all other properties to the default synthesised Codable conformance.

1 Like

Generally the recommended way is to use property wrapper, since the synthesis will use wrapper's implementation for en/decoding. The next step in case property wrapper is ill-suit is to reimplement the codable, though. I wouldn't really recomment implementing the full Codable since you'd then need to update it every time you add/remove fields. It is very fragile.

Thank you. I see that distinction now.

If you have to do value transformations, do they have to be done during decoding or could the data be decoded as-is and then transformed after the decode?

I don’t know the exact use-case here, but perhaps post-decode processing is an option.

A few others have offered some suggestions but to answer your original question — it's not currently possible to do this. I filed a Radar about this a long time ago and I believe @Xi_Ge or @akyrtzi might know if there's a public JIRA ticket on bugs.swift.org tracking this or any progress.

In the meantime, I believe 3rd-party tools like Sourcery might get you closer if property wrappers or other language tools don't quite do what you need.

One workaround I can think of (not tested):

Extend TopLevelDecoder with a new decode method, and Deodable with a new initialiser. Use Decodable's new initialiser to first call the old init(from:) within, then add customisation steps for the values you want to change. Give TopLevelDecoder's new decode method a default implementation that uses Deodable's a new initialiser.

Yes, I'm fully aware that Codable conformance is automatically synthesized by the compiler. I already mentioned that in my question. My question was about how to override the default manner in which properties are encoded and decoded only for certain properties without having to manually decode all of them.

A pattern I find helpful when only a few properties require custom decoding is the "private raw Codable type", as below:

struct MyStruct {
    var value: Int
    var processed: String // needs custom decoding!
}

extension MyStruct: Decodable {
    init(from decoder: Decoder) throws {
        struct Raw: Decodable {
            var value: Int
            var processed: String
        }
        let raw = try Raw(from: decoder)
        self.init(
            value: raw.value,
            processed: raw.processed + "rrr")
    }
}

let json = """
    { "value": 1, "processed": "foo" }
    """
let s = try JSONDecoder().decode(MyStruct.self, from: json.data(using: .utf8)!)

// Prints MyStruct(value: 1, processed: "foorrr")
print(s)
4 Likes

I'd point out that, in the scenario of this thread (large number of properties not needing special handling), the code to copy these properties to/from the private struct is about the same size (lots) and complexity (little) as writing the code to encode/decode the original struct.

Even so, it might be preferable, since it avoids defining the coding keys explicitly.

A possible first step towards this is to file a bug against Xcode asking for an Xcode shortcut to generate the source, like we already have in Xcode for generating automatically synthesized (internal) init methods.

-W

This feature is exactly what I'm looking for, but its omission doesn't count as a bug. Although, maybe I'll file a feature request.

What is this Xcode shortcut you refer to?

The code snippet that generates these?

Swift:

init(<#parameters#>) {
		<#statements#>
	}

ObjC:

- (instancetype)init
	{
		self = [super init];
		if (self) {
			<#statements#>
		}
		return self;
	}

??

The Swift one could be significantly smarter and include the actual defined properties in the struct/class using SwiftSyntax.

I'm wondering if there's something else you're referring to that's "smarter" and more useful than the above. TIA.

Terms of Service

Privacy Policy

Cookie Policy