I have a json response of an API. This returns a also value, which is a Dictionary. How to can I achieve to store / map only the value of this Dictionary. Here is an example which can be simply put into a playground:
id = ["$oid": "591ae6cb9d1fa2b6e47edc33"]
should only be
id = "591ae6cb9d1fa2b6e47edc33"
Here is an example which can be simply put into a playground:
import Foundation
struct Location : Decodable {
enum CodingKeys : String, CodingKey {
case id = "_id"
}
var id : [String:String]? // this should be only a string with the value of "$oid"
}
extension Location {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode([String:String]?.self, forKey: .id)
}
}
var json = """
[
{
"_id": {
"$oid": "591ae6cb9d1fa2b6e47edc33"
}
},
{
"_id": {
"$oid": "591ae6cd9d1fa2b6e47edc34"
}
}
]
""".replacingOccurrences(of: "}\n{", with: "}\\n{").data(using: .utf8)!
let decoder = JSONDecoder()
do {
let locations = try decoder.decode([Location].self, from: json)
locations.forEach { print($0) }
} catch {
print(error.localizedDescription )
}
You were almost there:
struct Location {
var id: String
}
extension Location: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "_id"
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let _id = try values.decode([String: String].self, forKey: .id)
id = _id["$oid"]!
}
}
If you have mote keys under _id in the JSON data, I'd strongly suggest you make a private struct that represents the struct for the benefit of type safety.
Related
I have geoJSON file:
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
40.303141,
55.9765684
],
[
40.3033449,
55.9765114
],
[
40.3034017,
55.976575
],
[
40.3031979,
55.9766321
],
[
40.303141,
55.9765684
]
]
]
]
},
"properties": {
"#id": 4305947573,
"building": "yes"
}
}
I'm interested in properties:
"properties":{"#id":4305947573,"building":"yes"}
I want parse "properties", and make structure:
struct Feature: Decodable {
let type: String
let properties: Dictionary<String, String> }
It's work good, but then i add parameter in geoJSON: "#id":4305947573
4305947573 - this is Int variable, and parser don't parse geoJSON.
I think i need modify my struct Feature. I want to parser understand and String, and Int in properties.
Help me please. Thank you
There are a number of GeoJSON swift libraries (search github) that you could use
instead of re-inventing the wheel.
If you really want to code it yourself, try this approach,
where the dynamic keys and values of properties are decoded
into a dictionary of var data: [String: Any], as shown.
Use the struct models like this:
let result = try JSONDecoder().decode(Feature.self, from: data)
Models
struct Feature: Decodable {
let type: String
var properties: Properties
// ...
}
struct Properties: Decodable {
var data: [String: Any] = [:]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicKey.self)
container.allKeys.forEach { key in
if let theString = try? container.decode(String.self, forKey: key) {
self.data[key.stringValue] = theString
}
if let theInt = try? container.decode(Int.self, forKey: key) {
self.data[key.stringValue] = theInt
}
}
}
}
struct DynamicKey: CodingKey {
var intValue: Int?
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = ""
}
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
}
Essentially I am decoding a JSON object with keys that could only be a few different values.
struct People: Decodable {
var name: String
var grade: String
var code: PersonID
enum PersonCodes: String, Decodable {
case In_Transit = "0",
Accepted = "1",
Exception = "2",
Delivered = "3"
}
}
The codes values in the JSON are numbers presented as Strings like "0", "1", "2" etc..
Each code has a meaning like In_Transit, Hired, Ready, All Set .. how can codes be outputed with spaces if enums does not allow spaces (I need to replace the _ with space).
Example of JSON:
{
"name" : "Jake",
"grade" : "A Grade"
"code" : "0"
}
Need for code 0 to be read as "In Transit"
you could try this approach (works for me):
struct People: Decodable {
var name: String
var grade: String
var code: PersonCodes
enum CodingKeys: String, CodingKey {
case name, grade, code
}
enum PersonCodes: String, Decodable {
case In_Transit = "0",
Accepted = "1",
Exception = "2",
Delivered = "3"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
grade = try container.decode(String.self, forKey: .grade)
let stringCode = try container.decode(String.self, forKey: .code)
if let dasCode = PersonCodes(rawValue: stringCode) {
code = dasCode
} else {
code = PersonCodes.In_Transit // <-- todo, pick a default
}
}
}
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear {
let json = """
{
"name" : "Jake",
"grade" : "A Grade",
"code" : "1"
}
"""
let data = json.data(using: .utf8)!
do {
let decoded = try JSONDecoder().decode(People.self, from: data)
print(decoded)
print(decoded.code)
print(decoded.code.rawValue)
} catch {
print("\(error)")
}
}
}
}
You could also use this, if you want a string description of the code (which is a String not an Int in the json data):
struct People: Decodable {
var name: String
var grade: String
var code: String
enum CodingKeys: String, CodingKey {
case name, grade, code
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
grade = try container.decode(String.self, forKey: .grade)
let stringCode = try container.decode(String.self, forKey: .code)
switch stringCode {
case "0": code = "In Transit"
case "1": code = "Accepted"
case "2": code = "Exception"
case "3": code = "Delivered"
default: code = "Unknown" // <-- todo default
}
}
}
I had data struct like this
{
"version": 1,
"profile": [
{
"type": "name",
"value": "Hellow"
},
{
"type": "email",
"value": "1#a.com"
},
]
}
Now I could decode it like this
struct Profile: Decodable {
let version: Int
let name: String
let email: String
struct Item: Decodable {
let type: String
let value: String
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
version = try container.decode(Int.self, forKey: .version)
let items = try container.decode([Item].self, forKey: .data)
name = items.first{$0.type == "name"}!.value
email = items.first{$0.type == "email"}!.value
}
}
Does there are any way to update Profile without rewrite init(from)?
Update
#dynamicMemberLookup can use like mapping function.
But it will lose autocomplete.
#dynamicMemberLookup
struct Profile: Decodable {
let version: Int
struct Item: Decodable {
let type: Keys
let value: String
}
let profile: [Item]
enum Keys: String, Decodable, ExpressibleByStringLiteral {
case name
case email
case undefined
init(stringLiteral: String) {
self = Keys(rawValue: stringLiteral) ?? .undefined
}
}
subscript(dynamicMember member: Keys) -> String {
return profile.first{$0.type == member}?.value ?? "undefined"
}
}
Codable is great when you know the key formatting of the JSON data. But what if you don't know the keys? I'm currently faced with this problem.
Normally I would expect JSON data to be returned like this:
{
"id": "<123>",
"data": [
{
"id": "<id1>",
"event": "<event_type>",
"date": "<date>"
},
{
"id": "<id2>",
"event": "<event_type>",
"date": "<date>"
},
]
}
But this is what I'm aiming to decode:
{
"id": "123",
"data": [
{ "<id1>": { "<event>": "<date>" } },
{ "<id2>": { "<event>": "<date>" } },
]
}
Question is: how do I use Codable to decode JSON where the keys are unique? I feel like I'm missing something obvious.
This is what I'm hoping to do so I can use Codable:
struct SampleModel: Codable {
let id: String
let data: [[String: [String: Any]]]
// MARK: - Decoding
enum CodingKeys: String, CodingKey {
case id = "id"
case data = "data"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
// This throws an error: Ambiguous reference to member 'decode(_:forKey:)'
data = try container.decode([[String: [String: Any]]].self, forKey: .data)
}
}
This throws an error: Ambiguous reference to member 'decode(_:forKey:)'
For your completely changed question, the solution is very similar. Your struct simply adds one additional layer above the array. There's no need for any custom decoding nor even any CodingKeys.
Note that you can't use Any in a Codable.
let json="""
{
"id": "123",
"data": [
{ "<id1>": { "<event>": "2019-05-21T16:15:34-0400" } },
{ "<id2>": { "<event>": "2019-07-01T12:15:34-0400" } },
]
}
"""
struct SampleModel: Codable {
let id: String
let data: [[String: [String: Date]]]
}
var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let res = try decoder.decode(SampleModel.self, from: json.data(using: .utf8)!)
print(res)
} catch {
print(error)
}
The original answer for your original question.
Since you have an array of nested dictionary where none of the dictionary keys are fixed, and since there are no other fields, you can just decode this as a plain array.
Here's an example:
let json="""
[
{ "<id1>": { "<event>": "2019-07-01T12:15:34-0400" } },
{ "<id2>": { "<event>": "2019-05-21T17:15:34-0400" } },
]
"""
var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let res = try decoder.decode([[String: [String: Date]]].self, from: json.data(using: .utf8)!)
print(res)
} catch {
print(error)
}
How does the Swift 4 Decodable protocol cope with a dictionary containing a key whose name is not known until runtime? For example:
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
Here we have an array of dictionaries; the first has keys categoryName and Trending, while the second has keys categoryName and Comedy. The value of the categoryName key tells me the name of the second key. How do I express that using Decodable?
The key is in how you define the CodingKeys property. While it's most commonly an enum it can be anything that conforms to the CodingKey protocol. And to make dynamic keys, you can call a static function:
struct Category: Decodable {
struct Detail: Decodable {
var category: String
var trailerPrice: String
var isFavorite: Bool?
var isWatchlist: Bool?
}
var name: String
var detail: Detail
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
static let name = CodingKeys.make(key: "categoryName")
static func make(key: String) -> CodingKeys {
return CodingKeys(stringValue: key)!
}
}
init(from coder: Decoder) throws {
let container = try coder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.detail = try container.decode([Detail].self, forKey: .make(key: name)).first!
}
}
Usage:
let jsonData = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
}
]
""".data(using: .utf8)!
let categories = try! JSONDecoder().decode([Category].self, from: jsonData)
(I changed isFavourit in the JSON to isFavourite since I thought it was a mispelling. It's easy enough to adapt the code if that's not the case)
You can write a custom struct that functions as a CodingKeys object, and initialize it with a string such that it extracts the key you specified:
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
Thus, once you know what the desired key is, you can say (in the init(from:) override:
let key = // whatever the key name turns out to be
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
So what I ended up doing is making two containers from the decoder — one using the standard CodingKeys enum to extract the value of the "categoryName" key, and another using the CK struct to extract the value of the key whose name we just learned:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here, then, is my entire Decodable struct:
struct ResponseData : Codable {
let categoryName : String
let unknown : [Inner]
struct Inner : Codable {
let category : String
let trailerPrice : String
let isFavourit : String?
let isWatchList : String?
}
private enum CodingKeys : String, CodingKey {
case categoryName
}
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
}
And here's the test bed:
let json = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
"""
let myjson = try! JSONDecoder().decode(
[ResponseData].self,
from: json.data(using: .utf8)!)
print(myjson)
And here's the output of the print statement, proving that we've populated our structs correctly:
[JustPlaying.ResponseData(
categoryName: "Trending",
unknown: [JustPlaying.ResponseData.Inner(
category: "Trending",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)]),
JustPlaying.ResponseData(
categoryName: "Comedy",
unknown: [JustPlaying.ResponseData.Inner(
category: "Comedy",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)])
]
Of course in real life we'd have some error-handling, no doubt!
EDIT Later I realized (in part thanks to CodeDifferent's answer) that I didn't need two containers; I can eliminate the CodingKeys enum, and my CK struct can do all the work! It is a general purpose key-maker:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CK.self)
self.categoryName = try! con.decode(String.self, forKey:CK(stringValue:"categoryName")!)
let key = self.categoryName
self.unknown = try! con.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here's what I eventually came up for this json:
let json = """
{
"BTC_BCN":{
"last":"0.00000057",
"percentChange":"0.03636363",
"baseVolume":"47.08463318"
},
"BTC_BELA":{
"last":"0.00001281",
"percentChange":"0.07376362",
"baseVolume":"5.46595029"
}
}
""".data(using: .utf8)!
We make such a structure:
struct Pair {
let name: String
let details: Details
struct Details: Codable {
let last, percentChange, baseVolume: String
}
}
then decode:
if let pairsDictionary = try? JSONDecoder().decode([String: Pair.Details].self, from: json) {
var pairs: [Pair] = []
for (name, details) in pairsDictionary {
let pair = Pair(name: name, details: details)
pairs.append(pair)
}
print(pairs)
}
It is also possible to call not pair.details.baseVolume, but pair.baseVolume:
struct Pair {
......
var baseVolume: String { return details.baseVolume }
......
Or write custom init:
struct Pair {
.....
let baseVolume: String
init(name: String, details: Details) {
self.baseVolume = details.baseVolume
......