I am creating a generic post body for my API's call, my post body is mostly same except the data parameter which is different for different API calls data acts like filler for different requirements, few below JSON post body.
Example 1:
{
"timeStampUtc": "2019-07-29T03:29:21.729Z",
...
"geoLocationInfo": {
"latitude": 0,
"longitude": 0,
...
},
"data": {
"loginIdentity": "string",
"loginPassword": "string"
}
}
Example 2:
{
"timeStampUtc": "2019-07-29T03:29:21.729Z",
...
"geoLocationInfo": {
"latitude": 0,
"longitude": 0,
...
},
"data": {
"wazId": 0,
"regionId": 0
}
}
Example 3:
{
"timeStampUtc": "2019-07-29T03:29:21.729Z",
...
"geoLocationInfo": {
"latitude": 0,
"longitude": 0,
...
},
"data": {
"loginIdentity": "string",
"wazID": 0
}
}
I am using the encodable and generics to overcome this requirement, well I am able to do the first two scenarios, but struggle with the third when the data has values of different types.
Following is the sample code can be directly tried in Playground
struct PostBody<T : Codable>: Codable
{
var deviceInfo = ""
var geoLocationInfo = ""
var data = Dictionary<String, T>()
enum CodingKeys: String, CodingKey
{
case deviceInfo, geoLocationInfo, data
}
init(dataDict : Dictionary<String, T>) {
self.data = dataDict
}
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy : CodingKeys.self)
deviceInfo = try container.decode(String.self, forKey: .deviceInfo)
geoLocationInfo = try container.decode(String.self, forKey: .geoLocationInfo)
data = try container.decode(Dictionary.self, forKey: .data)
}
func encode(to encoder : Encoder)
{
var container = encoder.container(keyedBy : CodingKeys.self)
do
{
try container.encode(deviceInfo, forKey : .deviceInfo)
try container.encode(geoLocationInfo, forKey : .geoLocationInfo)
try container.encode(data, forKey : .data)
}
catch
{
fatalError("Should never happen")
}
}
}
let postBody = PostBody<String>(dataDict : ["1" : "1", "2" : "2"])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(postBody)
let encodedDataDict2 = try encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)
let postBody1 = PostBody(dataDict : ["1" : 1, "2" : 2])
let encoder1 = JSONEncoder()
encoder1.outputFormatting = .prettyPrinted
try encoder1.encode(postBody1)
let encodedDataDict3 = try encoder1.encode(postBody1)
print(String(data : encodedDataDict3, encoding : .utf8)!)
Instead of generics use an enum for the different types. Feel free to add more types
enum StringOrInt : Codable {
case string(String), integer(Int)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let stringValue = try container.decode(String.self)
self = .string(stringValue)
} catch DecodingError.typeMismatch {
let integerValue = try container.decode(Int.self)
self = .integer(integerValue)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let stringValue): try container.encode(stringValue)
case .integer(let integerValue): try container.encode(integerValue)
}
}
}
struct PostBody: Codable
{
let deviceInfo, geoLocationInfo : String
let data : Dictionary<String, StringOrInt>
}
let postBody = PostBody(deviceInfo: "Foo", geoLocationInfo: "Bar", data : ["loginIdentity" : .string("string"), "wazID" : .integer(0)])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodedDataDict2 = try encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)
Swift 5.7.1 EDIT
it just works now:
struct PostBody<T: Codable>: Codable
{
var deviceInfo = ""
var geoLocationInfo = ""
var data = Dictionary<String, T>()
enum CodingKeys: String, CodingKey
{
case deviceInfo, geoLocationInfo, data
}
init(dataDict : Dictionary<String, T>) {
self.data = dataDict
}
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy : CodingKeys.self)
deviceInfo = try container.decode(String.self, forKey: .deviceInfo)
geoLocationInfo = try container.decode(String.self, forKey: .geoLocationInfo)
data = try container.decode(Dictionary<String, T>.self, forKey: .data)
}
func encode(to encoder : Encoder)
{
var container = encoder.container(keyedBy : CodingKeys.self)
do
{
try container.encode(deviceInfo, forKey : .deviceInfo)
try container.encode(geoLocationInfo, forKey : .geoLocationInfo)
try container.encode(data, forKey : .data)
}
catch
{
fatalError("Should never happen")
}
}
}
let postBody = PostBody<String>(dataDict : ["1" : "1", "2" : "2"])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(postBody)
let encodedDataDict2 = try encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)
// {
// "geoLocationInfo" : "",
// "deviceInfo" : "",
// "data" : {
// "2" : "2",
// "1" : "1"
// }
// }
let postBody1 = PostBody(dataDict : ["1" : 1, "2" : 2])
let encoder1 = JSONEncoder()
encoder1.outputFormatting = .prettyPrinted
try encoder1.encode(postBody1)
let encodedDataDict3 = try encoder1.encode(postBody1)
print(String(data : encodedDataDict3, encoding : .utf8)!)
// {
// "geoLocationInfo" : "",
// "deviceInfo" : "",
// "data" : {
// "2" : 2,
// "1" : 1
// }
// }
Whenever working with json data I recommend using QuickType as this will allow you to quickly get an idea or to simply just generate code necessary for the different languages that you need.
This is an example based on the data you provided.
There are several options to play with, such as changing between Class or Struct and only using plain types. There is also the option of generating initializers and mutators.
Related
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
}
}
}
The following two forms of data were successfully requested.
{
"ride_fare": 1000,
"km": 7
]
}
{
"ride_fare": 1000,
"km": 7,
"options": [ 0, 1, 2]
}
However, I don't know how to request a two-dimensional associative array like the one below.
How can I request it?
{
"ride_fare": 1000,
"km": 7,
"option_fares": [
{
"price": 200,
"name": "立ち寄り",
"id": 1
}
]
}
The code that I wrote:
var options = [Any]()
for option in optionFares {
let params = [
"id" : option.id ?? 0,
"name" : option.name ?? "",
"price" : option.price ?? 0
] as [String : Any]
options.append(params)
}
let faresData = [
"id" : driverOrder.id ?? 0,
"km" : driverOrder.distance ?? 0,
"option_fares" : options,
"ride_fare" : driverOrder.ride_fare ?? 0
] as [String : Any]
First, create a struct that matches the json format you want to request.
struct Params: Codable {
let rideFare, km: Int
let optionFares: [OptionFare]
enum CodingKeys: String, CodingKey {
case rideFare = "ride_fare"
case km
case optionFares = "option_fares"
}
}
struct OptionFare: Codable {
let price: Int
let name: String
let id: Int
}
And you must create a request parameter in Moya's task.
import Moya
extension APITarget: TargetType {
var task: Task {
case .yourCaseName(let price, let name, let id, let rideFare, let km):
let encoder: JSONEncoder = JSONEncoder()
let optionFareData: [OptionFare] = []
optionFareData.append(OptionFare(price, name, id))
let paramsData = Params(rideFare, km, optionFareData)
let jsonData: Data = try! encoder.encode(paramsData)
return .requestData(jsonData)
}
}
}
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
......
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.