Apple من Swift 4 بما يعني من 2017 اضافة طريقة جديدة للتعامل مع الـ JSON عن طريق عمل mapping بإستخدام Decodable
فكل الي عليك تعمل Struct بنفس هيكلة الـ json وتمرره الى JSONDecoder.decode
مع الـ data الي جاتك من الركويست وراح يعمل mapping بشكل تلقائي
الامور بسيطه وسهله الين ما تطيح في سيناريو غير متوقع !
عشان تضبط الطريقة السابقة لازم كل القيم تكون موجوده ودائما الـ response من السيرفر يكون بنفس الـ type للكل الـ keys
بما يعني هناك عدة سيناريوهات غير مهندله
– سيناريو الـ key يكون موجود احيانا واحيانا يكون غير موجود !
– سيناريو الـ type للمتغيرات يكون مختلف مثلا مره يكون Array ومره يكون String !
في حال الـ deocde واجه احدى المشكلتين السابقة راح يعطيك error مشابهه لهذا
Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key)
لتسهل الامور راح اعمل ٣ ملفات json
الاول كل الـ keys موجوده وكلها نوعها String بإسم errors
الثاني فقط key وحده موجوده ونوعها String باسم response
الثالث كل الـ keys موجوده لكن key الـ error راح يكون dictionary بإسم errors different type
الملف الاول راح يكون بهذا الشكل
{
"result": "Any data",
"error": "Field is Fequired"
}
الـ Model راح يكون بهذا الشكل
public struct ResponseModel: Codable, Error {
var result: String
var error: String
}
لفتح الملف وتحويله الى json راح نستخدم هذا الكود
لفتح الملف وتحويله الى json
راح نستخدم هذا الكود عند تشغيل الكود راح يشتغل بدون مشاكل وراح يطبع النتيجة بدون مشاكل
do {
guard let fileUrl = Bundle.main.url(forResource: "errors", withExtension: "json") else { fatalError() }
let json = try String(contentsOf: fileUrl, encoding: String.Encoding.utf8)
let jsonData = json.data(using: .utf8)!
print(getErrorMessageString(jsonData: jsonData) ?? "")
} catch {
print(error)
}
public struct ResponseModel: Codable, Error {
var result: String
var error: [String: [String]]
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
result = try container.decode(String.self, forKey: .result)
if let singleError = try? container.decode(String?.self, forKey: .error) {
error = ["error": [singleError]]
} else {
error = try container.decode([String:[String]]?.self, forKey: .error) ?? ["":[]]
}
}
}
func getErrorMessageString(jsonData: Data) -> ResponseModel? {
do {
let errorMessage = try JSONDecoder().decode(ResponseModel.self, from: jsonData)
return errorMessage
} catch {
print("ERROR:", error)
}
return nil
}

لكن الان اذا غيرنا الملف الى هذا الملف
راح تحصل على هذا الخطا
{
"result": "Any data",
}
do {
guard let fileUrl = Bundle.main.url(forResource: "response", withExtension: "json") else { fatalError() }
let json = try String(contentsOf: fileUrl, encoding: String.Encoding.utf8)
let jsonData = json.data(using: .utf8)!
print(getErrorMessageString(jsonData: jsonData) ?? "")
} catch {
print(error)
}

المشكله هنا انه حقل error غير موجود في ملف الـ json
تقدر تحل المشكله بطريقة سهله انك تكتب Custom decode
بحيث تهندل هذا السيناريو بهذا الشكل
الي عملته اني استخدمت decodeIfPresent في حال المتغير والقيمة موجوده راح يعملها Decode غير كذا راح يعطي لها قيمة String فاضيه
public struct ResponseModel: Codable, Error {
var result: String
var error: String
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
result = try container.decode(String.self, forKey: .result)
error = try container.decodeIfPresent(String.self, forKey: .error) ?? ""
}
}
الان عند تشغيل المشروع ماراح تواجه مشاكل

السيناريو الاخير
اذا الملف كان يحتوى على نوع مختلف مثل هذا راح تواجه هذا الخطأ
{
"result": "",
"error": {
"name": [
"The name is required."
],
"phone": [
"The phone is required."
],
"email": [
"The email is required."
]
}
}

حلها انك تحاول تعمل decode بنوع معين اذا ما زبط
تجرب تعملها بالنوع الاخر بما انه في هذا المثال مستخدم dictionary راح اغير قيمة error الى dictionary
public struct ResponseModel: Codable, Error {
var result: String
var error: [String: [String]]
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
result = try container.decode(String.self, forKey: .result)
if let singleError = try? container.decode(String?.self, forKey: .error) {
error = ["error": [singleError]]
} else {
error = try container.decode([String:[String]]?.self, forKey: .error) ?? ["":[]]
}
}
}
عند تشغيل المشروع لن تواجه اي مشكلة

الان لنجرب نرجع لاول ملف الي اسمه error
والـ type الـ error من نوع String
ايضا لن تواجه مشاكل، لكن النوع راح يتحول الى dictionary بسبب الكود السابق

طبعا في طرق اخرى زي تخلي النوع زي ماهو String
وبعدها تحول dictionary الى string مثلا عن طريق عمل loop واضافتها الى String جديد وعند انتهاء الـ loop ترجع الـ String
تقدر تسوي الطريقة الي تفضلها
