For my question I have prepared a simple SwiftUI project at GitHub.
A backend sends the following JSON data to my app, representing a game with a 15 x 15 letters:
{
"gid":266,
"letters":[
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,"H", null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,"U", null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,"E", null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]
],
"values":[
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null, 4, null,null,null,null,null,null,null],
[null,null,null,null,null,null,null, 1, null,null,null,null,null,null,null],
[null,null,null,null,null,null,null, 1, null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],
[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]
],
"tiles":[
{"col": 8, "row": 7, "value": 1, "letter": "E"},
{"col": 7, "row": 7, "value": 1, "letter": "U"},
{"col": 6, "row": 7, "value": 4, "letter": "H"}
]
}
As you can see the letters
and values
are 2-dimensional arrays of String?
and Int?
and that is how I have defined them in the GameModel.swift which I use for JSON parsing:
struct GameModel: Codable, Identifiable {
var id: Int32 { gid }
let gid: Int32
let letters: [[String?]]
let values: [[Int32?]]
let tiles: [TileModel]? // the previous move as an array
// create a new Core Data entity and copy the properties
func toEntity(viewContext: NSManagedObjectContext) -> GameEntity {
let gameEntity = GameEntity(context: viewContext)
gameEntity.gid = self.gid
gameEntity.letters = self.letters
gameEntity.values = self.values
gameEntity.tiles = self.tiles
return gameEntity
}
}
struct TileModel: Codable {
let col: Int
let row: Int
let value: Int
let letter: String
}
I am trying to parse them by using the Transformable
Core Data type and thus I have added these 3 lines to the Persistence.swift
let container: NSPersistentContainer
init(inMemory: Bool = false) {
ValueTransformer.setValueTransformer(ValuesToDataTransformer(), forName: .valuesToDataTransformer)
ValueTransformer.setValueTransformer(LettersToDataTransformer(), forName: .lettersToDataTransformer)
ValueTransformer.setValueTransformer(TilesToDataTransformer(), forName: .tilesToDataTransformer)
container = NSPersistentContainer(name: "TransApp")
Also I have added the 3 files:
My problem is that my custom ValueTransformer
sub classes do not compile.
The error is:
Static method 'unarchivedObject(ofClass:from:)' requires that '[[Int32?]]' conform to 'NSCoding'
and similar for the 2 others.
This is a completely different approach avoiding Transformable
. It saves only the TileModel
array as JSON string and uses computed properties to create the grids and to convert the custom type to and from JSON.
Actually you don't need to decode the grids at all.
In the entity declare tiles
as String
@NSManaged public var tiles: String?
Add a computed property to convert an array of TileModel
to JSON
var tileArray: [TileModel]? {
get {
guard let data = tiles?.data(using: .utf8) else { return nil }
return try? JSONDecoder().decode([TileModel].self, from: data)
}
set {
guard let data = try? JSONEncoder().encode(newValue) else { tiles = nil; return }
tiles = String(data: data, encoding: .utf8)
}
}
And the computed properties to create the grids, there is no reason to save them in the entity
var values : [[Int?]] {
var grid : [[Int?]] = [
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil]]
tileArray?.forEach{ grid[$0.col][$0.row] = $0.value }
return grid
}
var letters : [[String?]] {
var grid : [[String?]] = [
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil],
[nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil]]
tileArray?.forEach{ grid[$0.col][$0.row] = $0.letter }
return grid
}