Reputation: 1185
I am new to Swift and iOS development and I am currently learning how to work with JSON data.
I am trying to parse nested JSON data and display the following output:
White hex value is #ffffff
Black hex value is #000000
Gray10 hex value is #f5f7f8
Gray20 hex value is #e8eced
Gray30 hex value is #d5d9db
Gray40 hex value is #b6bec2
Gray50 hex value is #8e999e
Gray60 hex value is #69757a
Gray70 hex value is #495257
Gray80 hex value is #333a3d
Gray90 hex value is #1f2426
Purple10 hex value is #ffc7f2
Purple20 hex value is #f59de2
Purple30 hex value is #e07ecb
Purple40 hex value is #d160b7
Purple50 hex value is #b34fa0
Purple60 hex value is #964286
Purple70 hex value is #773569
Purple80 hex value is #5b284f
Purple90 hex value is #401c36
I am able to parse a simple JSON file such as this:
{
"color": {
"white": { "value": "#fff" },
"black": { "value": "#000" }
}
}
However, I cannot figure out how to correctly decode the nested data (seen below) into structs
and concatenate the gray
and purple
keys with their numeric intensity
keys (10-90) as seen in the desired output above.
Here is my playground code with nested JSON data that is not working:
import Foundation
let json = """
{
"color": {
"white": { "value": "#fff" },
"black": { "value": "#000" },
"gray": {
"10": { "value": "#f5f7f8" },
"20": { "value": "#e8eced" },
"30": { "value": "#d5d9db" },
"40": { "value": "#b6bec2" },
"50": { "value": "#8e999e" },
"60": { "value": "#69757a" },
"70": { "value": "#495257" },
"80": { "value": "#333a3d" },
"90": { "value": "#1f2426" }
},
"purple": {
"10": { "value": "#ffc7f2" },
"20": { "value": "#f59de2" },
"30": { "value": "#e07ecb" },
"40": { "value": "#d160b7" },
"50": { "value": "#b34fa0" },
"60": { "value": "#964286" },
"70": { "value": "#773569" },
"80": { "value": "#5b284f" },
"90": { "value": "#401c36" }
}
}
}
""".data(using: .utf8)!
struct color: Codable {
let value: String
struct intensity {
let value: String
}
}
struct Entry: Codable {
let color: [String: color]
}
let jsonDecoder = JSONDecoder()
do {
let parsedJSON = try jsonDecoder.decode(Entry.self, from: json)
for color in parsedJSON.color {
print("\(color.key) hex value is \(color.value.value)")
}
}
Ultimately this functionality will end up in an iOS app.
The (partial) JSON data shown in the code above is actual production data. It is being provided to me in that format. So I am unable to change how the JSON data is defined or nested.
I would appreciate some guidance on how to parse nested JSON data and display the data as shown above.
Upvotes: 1
Views: 176
Reputation: 51945
I used JSONSerialization and two structs for this, ColorData and ColorSaturation and handled much of the conversion in the init for ColorData
struct ColorData {
let name: String
let values: [ColorSaturation]
init?(name: String, values: Any) {
self.name = name
if let single = values as? [String: String] {
guard let colorValue = single["value"] else { return nil }
self.values = [ColorSaturation(level: nil, value: colorValue)]
} else if let colorValues = values as? [String: [String: String]] {
var values = [ColorSaturation]()
for (key, colorValue) in colorValues {
guard let level = Int(key), let value = colorValue["value"] else { continue }
values.append(ColorSaturation(level: level, value: value))
}
self.values = values
} else {
return nil
}
}
}
struct ColorSaturation {
let level: Int?
let value: String
}
And the decoding was done like this
do {
if let result = try JSONSerialization.jsonObject(with: data) as? [String: [String: Any]] {
let colors = result["color"].map { $0.map {colorData in ColorData(name: colorData.key, values: colorData.value) }}
print(colors)
}
} catch {
print(error)
}
Upvotes: 1
Reputation: 3886
Unless you know what the color names will be you can't go with Codable. However you can have some custom decoding logic to create an array of struct you define from the json above. Sample playground code may be like the one follows.
import UIKit
struct MyColor {
let name: String
let value: String
var console: String {
return name + " hex value is " + value
}
}
let json = "{\"color\":{\"white\":{\"value\":\"#fff\"},\"black\":{\"value\":\"#000\"},\"gray\":{\"10\":{\"value\":\"#f5f7f8\"},\"20\":{\"value\":\"#e8eced\"},\"30\":{\"value\":\"#d5d9db\"},\"40\":{\"value\":\"#b6bec2\"},\"50\":{\"value\":\"#8e999e\"},\"60\":{\"value\":\"#69757a\"},\"70\":{\"value\":\"#495257\"},\"80\":{\"value\":\"#333a3d\"},\"90\":{\"value\":\"#1f2426\"}},\"purple\":{\"10\":{\"value\":\"#ffc7f2\"},\"20\":{\"value\":\"#f59de2\"},\"30\":{\"value\":\"#e07ecb\"},\"40\":{\"value\":\"#d160b7\"},\"50\":{\"value\":\"#b34fa0\"},\"60\":{\"value\":\"#964286\"},\"70\":{\"value\":\"#773569\"},\"80\":{\"value\":\"#5b284f\"},\"90\":{\"value\":\"#401c36\"}}}}"
func parse() throws -> [MyColor] {
var myColors: [MyColor] = []
if let data = json.data(using: .utf8) {
if let root = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] {
if let colors = root["color"] as? [String:Any] {
for color in colors.keys {
if let valueNode = colors[color] as? [String:String] {
if let value = valueNode["value"] {
myColors.append(MyColor(name: color, value: value))
}
} else if let variants = colors[color] as? [String:[String:String]] {
for key in variants.keys {
if let variant = variants[key] {
if let value = variant["value"] {
myColors.append(MyColor(name: color + key, value: value))
}
}
}
}
}
}
}
}
return myColors
}
if let colors = try? parse() {
for color in colors {
print(color.console)
}
}
Hope it helps.
Upvotes: 1