Adding raw Data to the encoder - swift

I have an Encodable struct that contains raw Data. This data is expected to be a raw representation of a JSON string.
When I encode this struct, I expect the encoding to have that Data be part of the struct's JSON object.
Here's the playground I have come up with so far, with outlined expected and actual results:
import Foundation
struct Payload: Encodable {
let name: String
let contents: Data
enum CodingKeys: String, CodingKey {
case name
case contents
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(contents, forKey: .contents)
}
}
let payload = Payload(
name: "Hello",
/// Integers corresponding to ASCII values of a valid JSON string: `{"text":"C"}`
contents:Data([123, 34, 116, 101, 120, 116, 34, 58, 34, 67, 34, 125])
)
let encoder = JSONEncoder()
let data = try encoder.encode(payload)
let string = String(data: data, encoding: .utf8)!
print(string)
/// expected: `{"name":"Hello","contents": {"text":"C"} }`
/// actual: `{"name":"Hello","contents":"eyJ0ZXh0IjoiQyJ9"}`
I have also tried to encode contents as a String, but that results in:
{"name":"Hello","contents":"{\"text\":\"C\"}"}
Which is close, but still not what I want

You could try this approach, as shown in the example code, to encode and then recover your Payload contents Data as a String. Use the same approach for non-SwiftUI systems:
struct ContentView: View {
#State var payloadContents = ""
var body: some View {
Text(payloadContents)
.onAppear {
let payload = Payload(
name: "Hello",
/// Integers corresponding to ASCII values of a valid JSON string: `{"text":"C"}`
contents: Data([123, 34, 116, 101, 120, 116, 34, 58, 34, 67, 34, 125])
)
// direct access
// payloadContents = payload.stringContents!
do {
// encode the payload
let data = try JSONEncoder().encode(payload)
// decode the payload
let decoded = try JSONDecoder().decode(Payload.self, from: data)
print("\n---> decoded: \(decoded)") // <-- raw data
// get the `contents` to the desired String
payloadContents = String(data: decoded.contents, encoding: .utf8)!
// alternative
// payloadContents = decoded.stringContents!
print("\n---> payloadContents: \(payloadContents)\n")
} catch {
print(error)
}
}
}
}
struct Payload: Codable { // <-- here
let name: String
let contents: Data
var stringContents: String? {
String(data: contents, encoding: .utf8) // <-- here
}
enum CodingKeys: String, CodingKey {
case name, contents
}
}

Related

Decoding two different JSON responses with one struct? [duplicate]

This question already has answers here:
Decoding two different JSON responses with one struct using Codable
(2 answers)
Closed 1 year ago.
I'm receiving the same json structure from two endpoints, the only thing different are the keys in the json. On response #1 I get
[
{
"id": 45,
"chapter__book__name": "Alonso",
"chapter__book__id": 70,
"chapter__chapter": 2,
"verse": "",
"verse_number": 5,
"chapter": 97
},
]
And on response #2 I get:
[
{
"id": 962,
"book_name": "Title here",
"book_id": 70,
"chapter_number": 32,
"verse": "xxx",
"verse_number": 24,
"chapter": 127
},
]
Can one struct decode both of these? Currently my struct looks like this:
struct Verse: Decodable, Identifiable {
let id: Int
let book_name: String
let book_id: Int
let verse: String
let verse_number: Int
let chapter: Int // chapter Id in database
let chapter_number: Int
}
Which matches response #2, but not response #1.
#lorem ipsum's method should work I didn't try it myself with swiftUI, however it feels a bit convoluted to deal with 2 different types of object. Eventhough they share a common protocol, since it's the same object that will be decoded, it seems natural to keep track of one single type.
As stated by #Larme it can be done with a custom init(from decoder: Decoder) method.
import UIKit
let jsonA = """
[
{
"id": 45,
"chapter__book__name": "Alonso",
"chapter__book__id": 70,
"chapter__chapter": 2,
"verse": "",
"verse_number": 5,
"chapter": 97
},
]
"""
let jsonB = """
[
{
"id": 962,
"book_name": "Title here",
"book_id": 70,
"chapter_number": 32,
"verse": "xxx",
"verse_number": 24,
"chapter": 127
},
]
"""
protocol VerseCodingKey: CodingKey {
static var id: Self { get }
static var book_name: Self { get }
static var book_id: Self { get }
static var verse: Self { get }
static var verse_number: Self { get }
static var chapter: Self { get }
static var chapter_number: Self { get }
}
struct Verse: Decodable {
var id: Int
var book_name: String
var book_id: Int
var verse: String
var verse_number: Int
var chapter: Int
var chapter_number: Int
enum CodingKeysA: String, VerseCodingKey {
case id
case book_name
case book_id
case verse
case verse_number
case chapter
case chapter_number
}
enum CodingKeysB: String, VerseCodingKey {
case id
case book_name = "chapter__book__name"
case book_id = "chapter__book__id"
case verse
case verse_number
case chapter = "chapter__chapter"
case chapter_number = "chapter"
}
init(from decoder: Decoder) throws {
do {
try self.init(from: decoder, verseCodingKey: CodingKeysA.self)
return
} catch { }
do {
try self.init(from: decoder, verseCodingKey: CodingKeysB.self)
return
} catch { }
throw CustomError.unmatchedCodingKeys
}
init<T: VerseCodingKey>(from decoder: Decoder, verseCodingKey: T.Type) throws {
do {
let values = try decoder.container(keyedBy: T.self)
id = try values.decode(Int.self, forKey: .id)
book_name = try values.decode(String.self, forKey: .book_name)
book_id = try values.decode(Int.self, forKey: .book_id)
verse = try values.decode(String.self, forKey: .verse)
verse_number = try values.decode(Int.self, forKey: .verse_number)
chapter = try values.decode(Int.self, forKey: .chapter)
chapter_number = try values.decode(Int.self, forKey: .chapter_number)
} catch {
throw CustomError.missingCodingKey
}
}
}
enum CustomError: Error {
case missingCodingKey
case unmatchedCodingKeys
}
let dataA = jsonA.data(using: .utf8)!
let dataB = jsonB.data(using: .utf8)!
let verseA = try? JSONDecoder().decode([Verse].self, from: dataA)
let verseB = try? JSONDecoder().decode([Verse].self, from: dataB)
This code works on playground
SideNotes:
The whole point is to juggle with two different CodingKeys.
since this evolution it is now feasible to make an enum conform to protocols, which I didn't now of before diving into your issue. This makes the code more straightforward and reusable.
There may be a better way to handle the do catch mechanism but it's acceptable at this point. as stated by #Cristik in comment, you should enhance the error handling mechanism because you don't want to let all the error going through. see his comment below
This is how far I could get with this little experiment, I reckon someone will be able to do better. It still seem more reliable to use a single concrete class instead of two plus a protocol, but again, I'm not pretending to be an expert.

Swift - Expected to decode Array<Any> but found a dictionary instead

I have a json like bellow:
object{2}
status: 1
result{3}
cohorts[23]
categories[158]
languages[16]
And I am Decoder it like bellow:
struct ResultAPIJSON: Decodable {
private enum RootCodingKeys: String, CodingKey {
case result
}
private enum FeatureCohortsCodingKeys: String, CodingKey {
case cohorts
}
var cohortsPropertiesArray = [CohortsProperties]()
init(from decoder: Decoder) throws {
let rootContainerCohorts = try decoder.container(keyedBy: RootCodingKeys.self)
var featuresContainerCohorts = try rootContainerCohorts.nestedUnkeyedContainer(forKey: .result)
let AAA = try featuresContainerCohorts.nestedContainer(keyedBy: FeatureCohortsCodingKeys.self)
let BBB = try AAA.nestedUnkeyedContainer(forKey: .cohorts)
while BBB.isAtEnd == false {
let propertiesContainer = try featuresContainerCohorts.nestedContainer(keyedBy: FeatureCohortsCodingKeys.self)
// Decodes a single quake from the data, and appends it to the array.
let properties = try propertiesContainer.decode(CohortsProperties.self, forKey: .cohorts)
cohortsPropertiesArray.append(properties)
}
}
}
But get me bellow error:
typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
If this is your JSON
object{2}
status: 1
result{3}
cohorts[23]
categories[158]
languages[16]
(or more likely, this:)
{
"status": 1,
"result": {
"cohorts": 23,
"categories": 158,
"languages": 16
}
}
then you need two structs:
struct object: Decodable {
let status: Int
let result: Result
}
struct Result: Decodable {
let cohorts: Int
let categories: Int
let languages: Int
}

Swift Codable support for objects represented as array

I'm trying to encode and decode data from an API that represent an object as an array of strings, for instance:
[
["username", "message", "date"],
["username", "message", "date"],
["username", "message", "date"]
]
This is the corresponding Codable struct:
struct Message: Codable {
let user: String
let content: String
let date: String
private enum CodingKeys: Int, CodingKey {
case user = 0
case content = 1
case date = 2
}
}
Neither encoding or decoding work; encoding shows that a JSON object is created instead of an array:
let msg = Message(user: "foo", content: "content", date: "2019-06-04")
let jsonData = try! JSONEncoder().encode(msg)
let jsonString = String(data: jsonData, encoding: .utf8)!
The final string is:
{"content":"content","user":"foo","date":"2019-06-04"}
My goal is to obtain the following string
["foo", "content", "2019-06-04"]
Using a custom encode/decode method in the struct solves this, but forces to create a lot of boilerplate for each struct/class.
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let values = try container.decode([String].self)
user = values[CodingKeys.user.rawValue]
content = values[CodingKeys.content.rawValue]
date = values[CodingKeys.date.rawValue]
}
How would one proceed to support this for any object?
And yes, this is a weird API, but this is not the first time I encounter one of those and user a different API format is not what I'm looking for here.
Rather than singleValueContainer use unkeyedContainer, it's more robust. If you want to assign the array items to struct members you have to write a custom initializer anyway
struct Message: Codable {
let user: String
let content: String
let date: String
init(from decoder: Decoder) throws {
var arrayContainer = try decoder.unkeyedContainer()
guard arrayContainer.count == 3 else { throw DecodingError.dataCorruptedError(in: arrayContainer, debugDescription: "The array must contain three items") }
user = try arrayContainer.decode(String.self)
content = try arrayContainer.decode(String.self)
date = try arrayContainer.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var arrayContainer = encoder.unkeyedContainer()
try arrayContainer.encode(contentsOf: [user, content, date])
}
}

Convert an Int JSON value to String enum case

Here's an simplified version of the class:
class Movie: Codable {
var name: String
var genre: MovieGenre
init(name: String, genre: MovieGenre) {
self.name = name
self.genre = genre
}
}
enum MovieGenre: String, Codable {
case action
case drama
case horror
}
And the JSON:
{
"name" : "Test",
"genre" : 1
}
I know the relation between the JSON genre value and the MovieGenre enum is:
1 = action
2 = drama
3 = horror
Using JSONDecoder, how can I convert the JSON genre Int value to my enum MovieGenre?
I would like not to have to write an init from decoder, because it would be very verbose having to convert each attribute manually.
Here's an example:
let movie = Movie(name: "Test", genre: .action)
let jsonEncoder = JSONEncoder()
let jsonDecoder = JSONDecoder()
do {
// encoding
let jsonData = try jsonEncoder.encode(movie)
let jsonString = String(data: jsonData, encoding: .utf8)
print("JSON String : " + jsonString!) // prints: JSON String : {"name":"Test","genre":"action"}
// decoding
let json = "{\"name\":\"Test\",\"genre\":1}".data(using: .utf8)!
_ = try jsonDecoder.decode(Movie.self, from: json)
} catch {
print(error) // prints: typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "genre", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
}
Your type for your enum does not match with the type in the JSON.
I changed the Type for your enum to Int and set the initial value for action (as the default would be 0) and got the expected result without custom decoding.
Trying this in a playground:
import UIKit
import PlaygroundSupport
let jsonData = """
{
"name" : "Test",
"genre" : 1
}
""".data(using: .utf8)!
class Movie: Codable {
var name: String
var genre: MovieGenre
init(name: String, genre: MovieGenre) {
self.name = name
self.genre = genre
}
}
enum MovieGenre: Int, Codable {
case action = 1
case drama
case horror
}
let decoder = JSONDecoder()
let result = try? decoder.decode(Movie.self, from: jsonData)
print(result?.name)
print(result?.genre)
print(result?.genre.rawValue)
Output is:
Optional("Test")
Optional(__lldb_expr_39.MovieGenre.action)
Optional(1)
This should also encode in the same way.

Unable to parse response with Swift Codable

Unable to decode json response from server with Decodable
A help or a suggestion would be appreciated
JSON:
*
["error": <__NSArrayM 0x60400044ab60>(
)
, "data": <__NSArrayM 0x60400044fae0>(
{
id = 0;
name = all;
},
{
id = 1;
name = "MONTHLY SUPPLIES";
}
)
, "success": 1]
//Response is in Dictionary of Array
Code:
struct CategoryData: Decodable {
var categories: [Category]! // Array
//codable enum case
private enum DataKeys: String, CodingKey {
case data
}
// Manually decode values
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DataKeys.self)
let data = try container.decode([[String: String]].self, forKey: .data)
print(data)
/* Category is a class here contains 2 variable name and id.*/
categories = data.map({ Category($0) })
print(categories)
}
}
Juse make your Category structure conform to Codable. You should also map categories to "data".
//: Playground - noun: a place where people can play
import Foundation
struct CategoryData: Codable {
let categories: [Category]
private enum CodingKeys: String, CodingKey {
case categories = "data"
}
}
struct Category: Codable {
let id: Int
let name: String
}
// create json mock by encoding
let category1 = Category(id: 0, name: "all")
let category2 = Category(id: 1, name: "MONTHLY SUPPLIES")
let categoryData = CategoryData(categories: [category1, category2])
let json = try! JSONEncoder().encode(categoryData)
print(String(bytes: json, encoding: String.Encoding.utf8)) // Optional("{\"data\":[{\"id\":0,\"name\":\"all\"},{\"id\":1,\"name\":\"MONTHLY SUPPLIES\"}]}")
// create category data by decoding json (your actual question)
do {
let categoryDataAgain = try JSONDecoder().decode(CategoryData.self, from: json)
for category in categoryDataAgain.categories {
print(category.id) // 0, 1
print(category.name) // "all", "MONTLY SUPPLIES"
}
} catch {
print("something went wrong")
}