Codable ile generic obje ayıklama

Talip BÖKE
Nov 3 · 3 min read

Özellikle komplex sayfaların tasarımında kullanılan obje tiplerinin sabit olmadığı JSON ayıklama işlemini XCode Playground ‘ da ele alalım.

import UIKitlet jsonString = """
{
"rowList": [
{
"type":"person",
"data":{
"name":"Talip BÖKE",
"job":"iOS Developer"
}
},
{
"type":"song",
"data":{
"name":"One",
"singer":"Metallica"
}
},
{
"type":"car",
"data":{
"name":"Ibiza",
"brand":"Volkswagen"
}
}
]
}
"""
let jsonData = Data(jsonString.utf8)

JSON ‘ ı incelediğimizde rowList içerisindeki data katmanının modelinin yukarıdaki type’ın karşısında gönderildiğini görmekteyiz.Bu yapıyı ayıklarken JSON içerisinde olabilecek kaç farklı model tipi var ise hepsini modelliyoruz

Miras Kullanımı

Person,Song,Car modellerimiz hepsi name değişkenine sahip olduğu için özellikle miras alacakları bir model oluşturmamız daha iyi bir altyapı kurgulamamızı sağlayacaktır.

Note : Codable ile miras kullandığımız objelerde CodingKey ve required init kullanmaya dikkat ederek tüm case ‘leri yazmalıyız.

class BaseInnerData : Decodable {
var name : String?

private enum BaseInnerDataKeys : String , CodingKey {
case name
}

required init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: BaseInnerDataKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}

Diğer modelleri oluşturalım

class Person : BaseInnerData {
var job : String?

private enum PersonKeys : String , CodingKey {
case job
}

required init(from decoder : Decoder) throws {
try super.init(from : decoder)
let container = try decoder.container(keyedBy: PersonKeys.self)
self.job = try container.decode(String.self, forKey: .job)
}
}
class Song : BaseInnerData {
var singer : String?

private enum SongKeys : String , CodingKey {
case singer
}

required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container = try decoder.container(keyedBy: SongKeys.self)
self.singer = try container.decode(String.self, forKey: .singer)
}
}
class Car : BaseInnerData {
var brand : String?

private enum CarKeys : String , CodingKey {
case brand
}

required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container = try decoder.container(keyedBy: CarKeys.self)
self.brand = try container.decode(String.self, forKey: .brand)
}
}

Obje tipleri için Codable ‘ a uygun bir enum oluşturarak JSON üzerindeki type değişkenini enum’a çevirmek için kullanalım.

enum RowType : String , Decodable{
case person = "person"
case song = "song"
case car = "car"
}

Type ‘ a göre parse işlemini yapmak için Row modelimizi oluşturalım.

class Row : Decodable {
var type : RowType?
var data : BaseInnerData?

private enum DataRowKeys : String , CodingKey {
case type
case data
}

required init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: DataRowKeys.self)
self.type = try container.decode(RowType.self, forKey: .type)

switch type{
case .person?:
self.data = try container.decode(Person.self, forKey: .data)
case .song?:
self.data = try container.decode(Song.self, forKey: .data)
case .car?:
self.data = try container.decode(Car.self, forKey: .data)
default:
break
}
}
}
class MainResponse : Decodable {
var rowList : [Row]?
}

İşlemi kontrol etmek için gerekli kodları yazalım.

let jsonDecoder = JSONDecoder()do{
let result = try jsonDecoder.decode(MainResponse.self, from:jsonData)

let person = result.rowList?.first?.data as? Person
print(result.rowList?.first?.type?.rawValue)
print(person?.name)
print(person?.job)
print("\n")

let song = result.rowList?[1].data as? Song
print(result.rowList?[1].type?.rawValue)
print(song?.name)
print(song?.singer)
print("\n")

let car = result.rowList?.last?.data as? Car
print(result.rowList?.last?.type?.rawValue)
print(car?.name)
print(car?.brand)
}catch{
print(error)
}

Tüm alanların çıktısını görebiliriz.

Generic Yaklaşımı

Objelerin yönlendirmesini direk olarak Row sınıfından yönetmek yerine RowType enum’ından yönetmek işimizi daha fazla kolaylaştıracağı gibi tip bağımsız olarak Row sınıfını kullanabilmemizi sağlayacak.Convertable isimli generic protocol ile BaseInnerData yerine DataType takma adını kullanacağız.

protocol Convertable {
associatedtype DataType
func getObject<Key : CodingKey>(container : KeyedDecodingContainer<Key> , key : Key) throws -> DataType
}

RowType’ıda Convertable’dan türetip DataType ‘ a istediğimiz tipi verdik.

enum RowType : String , Decodable , Convertable{

typealias DataType = BaseInnerData
case person = "person"
case song = "song"
case car = "car"

func getObject<Key : CodingKey>(container : KeyedDecodingContainer<Key> , key : Key) throws -> DataType{
switch self {
case .person:
return try container.decode(Person.self, forKey: key)
case .song:
return try container.decode(Song.self, forKey: key)
case .car:
return try container.decode(Car.self, forKey: key)
}
}
}

MainResponse ve Row sınıflarınıda revize ettiğimizde

class MainResponse : Decodable {
var rowList : [Row<RowType>]?
}
class Row<Enum : Decodable & Convertable> : Decodable {
var type : Enum?
var data : Enum.DataType?

private enum DataRowKeys : String , CodingKey {
case type
case data
}

required init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: DataRowKeys.self)
self.type = try container.decode(Enum.self, forKey: .type)
self.data = try type?.getObject(container: container, key: .data)
}
}

generic olarak Row sınıfını kullanabilmekteyiz.

Sonuç Olarak

Yapıyı generic olarak kurguladığımızda da aynı sonucu elde etmekteyiz.Burada ekstra olarak yapılması gereken JSON’daki string ‘ lerin direk olarak objelere yansıma ile dönüştürülmesi işlemidir.Bununla ilgili gerekli araştırmayı yaptıktan sonra yazının devamına Codable ile yapılabiliyorsa ekleyeceğim :F.

Kendinize İyi Bakın :D

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade