I have a JSON string, I want to parse it like ObjectMapper Using Codable Protocol.
struct Health: Mappable {
var size: [String : Any] = [:]?
var name: Double?
init?(map: Map) {
}
mutating func mapping(map: Map) {
size <- map["health.size"]
name <- map["health.name"]
}
}
I want to eliminate Health struct model with direct access, as creating each model struct for different properties.
let jsonString = """
{
"health": {
"size":{
"width":150,
"height":150
},
"name":"Apple"
}
}
"""
I want to access properties with (.) Dot operator like health.size without creating Struct Model for Health.
struct HealthType: Codable {
var health: Health
}
struct Health: Codable {
var title: String
var size: Size
enum CodingKeys: String, CodingKey
{
case title = "name"
}
}
struct Size: Codable {
var width: Double
var height: Double
}
To do this, you need to implement the Codable
protocol yourself. It's not too difficult:
Try the following on Playground.
import Foundation
struct HealthType: Codable {
let title: String
let width: Double
let height: Double
enum CodingKeys: String, CodingKey
{
case health = "health"
case title = "name"
case width = "width"
case height = "height"
case size = "size"
}
}
extension HealthType {
init(from decoder: Decoder) throws {
let healthTypeContainer = try decoder.container(keyedBy: CodingKeys.self)
let health = try healthTypeContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .health)
let size = try health.nestedContainer(keyedBy: CodingKeys.self, forKey: .size)
let title = try health.decode(String.self, forKey: .title)
let width = try size.decode(Double.self, forKey: .width)
let height = try size.decode(Double.self, forKey: .height)
self.init(title: title, width: width, height: height)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var health = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .health)
var size = health.nestedContainer(keyedBy: CodingKeys.self, forKey: .size)
try health.encode(title, forKey: .title)
try size.encode(width, forKey: .width)
try size.encode(height, forKey: .height)
}
}
let jsonData = """
{
"health": {
"size":{
"width":150,
"height":150
},
"name":"Apple"
}
}
""".data(using: .utf8)!
do {
print(jsonData)
let healthType = try JSONDecoder().decode(HealthType.self, from: jsonData)
print(healthType.title) // Apple
print(healthType.width) // 150.0
print(healthType.width) // 150.0
} catch {
print(error)
}
You can almost make this work. However, JSONDecoder
operates on Data
instead of String
, a Playground is encoded using UTF-8 by default so the following Playground will run:
import Cocoa
let jsonData = """
{
"health": {
"size":{
"width":150,
"height":150
},
"name":"Apple"
}
}
""".data(using: .utf8)!
struct HealthType: Codable {
var health: Health
}
struct Health: Codable {
var title: String
var size: Size
enum CodingKeys: String, CodingKey
{
case title = "name"
case size
}
}
struct Size: Codable {
var width: Double
var height: Double
}
do {
let health = try JSONDecoder().decode(HealthType.self, from:jsonData)
print(health)
let h = health.health
print(h.title)
} catch {
print(error)
}
While this parses and runs well I cannot make sense of your statement "without creating Struct Model for Health". Part of the tradeoff of using Codable
is that you will have to provide structure definitions of the relevant part of your JSON. You can also parse your input into [String:Any].self
, but working with it is a drag. You will have to constantly evaluate casts and optionals. When using the Codable
protocol for parsing your errors will all be concentrated into the things decode
may throw
at you. The info you get concerning is fairly good at describing the faults in your JSON (or your struct
depending on your point of view).
To cut a long story short: As long as your JSON contains your "health"
key you will have to tell it what to do with it.
If your intention is not expose the properties that I don't need then one way is to privately decode the whole structure and then make only those properties available that you are going to expose in the outer world.
struct Health {
let size: Size
let title: String
struct Size: Decodable {
let width: Int
let height: Int
}
private struct RawResponse: Decodable {
let health: PrivateHealth
struct PrivateHealth: Decodable {
let size: Size
let name: String
}
}
}
// Decodable requirement is moved to extension so that default initializer is accessible
extension Health: Decodable {
init(from decoder: Decoder) throws {
let response = try RawResponse(from: decoder)
size = response.health.size
title = response.health.name
}
}
Usage:
let jsonData = """
{
"health": {
"size":{
"width":150,
"height":150
},
"name":"Apple"
}
}
""".data(using: .utf8)!
do {
let health = try JSONDecoder().decode(Health.self, from: jsonData)
print(health.size) // Size(width: 150, height: 150)
print(health.title) // Apple
} catch {
print(error.localizedDescription)
}
If you also need to implement the Encodable
protocol so that you can encode your data like the actual response then you can do it with:
extension Health: Encodable {
func encode(to encoder: Encoder) throws {
let health = Health.RawResponse.PrivateHealth(size: size, name: title)
let response = RawResponse(health: health)
var container = encoder.singleValueContainer()
try container.encode(response)
}
}
For this to work, you will need to make your
RawResponse
,PrivateHealth
andSize
struct to be Encodable too
private struct RawResponse: Encodable, Decodable {
struct PrivateHealth: Encodable, Decodable {
struct Size: Encodable, Decodable {
Example of encoding:
let health = Health(size: Health.Size(width: 150, height: 150), title: "Apple")
do {
let encodedHealth = try JSONEncoder().encode(health) // Encoded data
// For checking purpose, you convert the data to string and print
let jsonString = String(data: encodedHealth, encoding: .utf8)!
print(jsonString) // This will ensure data is encoded as your desired format
} catch {
print(error.localizedDescription)
}
Hey I created KeyedCodable and I think it is exactly what you are looking for. Your implementation will look like this.
struct Health: Codable, Keyedable {
var size: [String: Int]!
var name: String?
mutating func map(map: KeyMap) throws {
try size <-> map["health.size"]
try name <-> map["health.name"]
}
init(from decoder: Decoder) throws {
try KeyedDecoder(with: decoder).decode(to: &self)
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.