[swift-evolution] Question regarding SE-0167 Swift Encoders

Gwendal Roué gwendal.roue at gmail.com
Mon May 29 06:51:55 CDT 2017


Hello,

I have already asked stupid questions about SE-0167 and SE-0166, but this time I hope this is a real one.

According so SE-0166, codable types themselves instantiate a single value decoder, or a keyed container:

	public struct Farm : Codable {
		public init(from decoder: Decoder) throws {
			let container = try decoder.container(keyedBy: CodingKeys.self
			...
		}
	}

	public enum Animal : Int, Codable {
		public init(from decoder: Decoder) throws
		        let intValue = try decoder.singleValueContainer().decode(Int.self)
			...
		}
	}
	
According to SE-0167, decoder decode non-trivial types in their decode(_:forKey:) and decodeIfPresent(_:forKey:) methods:

	func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable
	func decodeIfPresent<T>(_ type: T.Type, forKey key: Key) throws -> T? where T : Decodable

My trouble is that the decoder does not know whether the Decodable type will ask for a keyed container, or for a single value container.

Why is it a problem?

In the context of decoding of SQL rows, keys may refer to different things, depending on whether we are decoding a *value*, or a *complex object*:

- for values, keys are column names, as everybody can expect
- for complex objects, keys are names of "row scopes". Row scopes are a concept introduced by GRDB.swift and allows a type that knows how to consume `SELECT * FROM table1` to consume as well the results of `SELECT table1.*, table2.* FROM table1 JOIN table2` through a "scope" that presents the row in the shape expected by the consumer (here, only columns from table1).

This is supposed to allow support for types that contain both nested types and values (one of the goals of SE-0166 and SE-0167):

	struct Compound : Codable {
		let someStruct: SomeStruct // object that feeds on the "someStruct" scope
		let name: String // value that feeds on the "name" column
	}

The two decoding methods decode(_:forKey:) and decodeIfPresent(_:forKey:) can't be implemented nicely, because they don't know whether the decodable type will ask for a keyed container or a single value container, and thus they don't know whether they should look for the presence of a row scope, or of a column:

A workaround is to perform runtime checks on the GRDB protocols adopted by T, as below. But it's still impossible to support other codable types:

	if let valueType = T.self as? DatabaseValueConvertible.Type {
		// if column is missing, trigger the "missing key" error or return nil.
	} else if let complexType = T.self as? RowConvertible.Type {
		// if row scope is missing, trigger the "missing key" error or return nil.
	} else {
		// don't know what to do
		fatalError("unsupported")
	}

Do you have any advice?

Gwendal Roué




More information about the swift-evolution mailing list