GRDB.swift: GRDB Does not honor the Swift 4 Decodable CodingKey

What did you do?

I mapped the incoming JSON key from uuid to categoryUuid using an enum CodingKeys

public struct Book: Codable {
    var userId: Int64?
    public var categoryUuid: String
    public var title: String
    public var languageId: String
    public var localizedTitle: String

    enum CodingKeys: String, CodingKey {
        case userId
        case categoryUuid = "uuid"
        case title
        case languageId
        case localizedTitle
    }
}

I created an SQLite table with the key categoryUuid

            do {
                try db.create(table: "book") { bookTable in
                    print("created: \(bookTable)")
                    bookTable.column("categoryUuid", .text).primaryKey()
                    bookTable.column("title", .text)
                    bookTable.column("languageId", .text)
                    bookTable.column("localizedTitle", .text)
                    bookTable.column("userId", .integer).references("user", onDelete: .cascade)
                }
            }
            catch {
                print("error making book table: \(error)")
            }

I extended the Book struct to contain an enum Columns

extension Book {
    enum Columns {
        static let userId = Column("userId")
        static let categoryUuid = Column("categoryUuid")
        static let title = Column("title")
        static let languageId = Column("languageId")
        static let localizedTitle = Column("localizedTitle")
    }
}

I adopted RowConvertible:

extension Book: RowConvertible { }

I override didInsert:

extension Book: MutablePersistable {
    public static let databaseTableName = "book"
    public mutating func didInsert(with rowID: String, for column: String?) {
        categoryUuid = rowID
    }
}

Using a Moya Target, I mapped the incoming JSON to a BookResponse(which contains a Book array)

        return self.kjvrvgNetworking.rx.request(.books(languageId: L10n.shared.language))
        .map { response -> BookResponse in try! response.map(BookResponse.self) }
            .flatMap { bookResponse -> Single<[Book]> in Single.just(bookResponse.result) 
            .flatMap { [unowned self] in self.replacePersistedBooks($0) }

Upon insertion(replacePersistedBooks()), I get the SQLite error:

2018-02-05 09:20:28.662544-0500 All Scripture[47115:782639] [logging] table book has no column named uuid
SQLite error 1 with statement `INSERT INTO "book" ("title", "userId", "uuid", "languageId", "localizedTitle") VALUES (?,?,?,?,?)`: table book has no column named uuid
Check user products failed with error: SQLite error 1 with statement `INSERT INTO "book" ("title", "userId", "uuid", "languageId", "localizedTitle") VALUES (?,?,?,?,?)`: table book has no column named uuid

What did you expect to happen?

I expected GRDB to insert a row using a value named categoryUuid and NOT uuid

What happened instead?

GRDB inserted a row using a value named uuid and NOT categoryUuid

Environment

**GRDB flavor(s):GRDB.swift, RxGRDB **GRDB version: see below

Using GRDB.swift (2.4.1)
Using RxGRDB (0.7.0)

**Installation method: CocoaPods **Xcode version:9.2 **Swift version:4 **Platform(s) running GRDB:iOS **macOS version running Xcode: High Sierra

Demo Project

Please link to or upload a project we can download that reproduces the issue.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 15 (5 by maintainers)

Most upvoted comments

Apologies for my late reply @groue Thank you for clarifying that for me, was exactly what I needed 😄

Hello @Cooparr,

Yes, only custom implementations of Decodable/Encodable requirements can deal with distinct coding keys for JSON and the database (or any other coding source/destination). See The userInfo Dictionary for more information.

Alternatively, you can just not rely on Codable for the database.

This is abundantly documented:

For example:

struct Player {
    var id: String
    var name: String
    var score: Int?
}

extension Player {
    enum Columns: String, ColumnExpression {
        // Actual database column names
        var id, name, score
    }
}

extension Player: FetchableRecord {
    init(row: Row) {
        id = row[Columns.id]
        name = row[Columns.name]
        score = row[Columns.score]
    }
}

extension Player: PersistableRecord {
    func encode(to container: inout PersistenceContainer {
        container[Columns.id] = id
        container[Columns.name] = name
        container[Columns.score] = score
    }
}