How to parse JSON with custom parameters using Codable protocol - json

I have a JSON with keys
{
"yearOfManufacture":"20/9/2018",
"carSize":8,
"isNew":true,
"carAssets":[
{
"color":"5761807993001",
"nativeId":"{\"app\":\"1234/Car/Native_App\",\"web\":\" /8888/Car/Native_Car_Desktop\"}"
}
]
}
I am trying to parse using Codable protocol with struct models
struct Cars: Codable {
var yearOfManufacture: String?
var carSize: Int = 0
var isNew: Bool = true
var carAssets: [CarAssests]?
}
struct CarAssests: Codable {
var color: String?
var nativeId: String?
}
I am getting error like The data couldn’t be read because it isn’t in the correct format. I tried using CodingKeys with decoder container not getting the exact type of "nativeId": "{\"app\":\"1234/Car/Native_App\",\"web\":\" /8888/Car/Native_Car_Desktop\"}" not getting exact data type of this.
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .deferredToData
if let jsonData = jsonString.data(using: .utf8) {
do {
print(jsonData)
let assets = try decoder.decode(Cars.self, from: jsonData)
print(assets)
} catch {
print(error.localizedDescription)
}
}

I bet you are doing something like this:
let jsonString = """
{
"yearOfManufacture": "20/9/2018",
"carSize": 8,
"isNew": true,
"carAssets": [
{
"color": "5761807993001",
"nativeId": "{\"app\":\"1234/Car/Native_App\",\"web\":\" /8888/Car/Native_Car_Desktop\"}"
}
]
}
"""
In a multiline string, both \" and " mean the character ". So you have to write \\" to get the two characters \ and ":
let jsonString = """
{
"yearOfManufacture": "20/9/2018",
"carSize": 8,
"isNew": true,
"carAssets": [
{
"color": "5761807993001",
"nativeId": "{\\"app\\":\\"1234/Car/Native_App\\",\\"web\\":\\" /8888/Car/Native_Car_Desktop\\"}"
}
]
}
"""

Related

Convert array elements to suitable json in Swift

I have an array of numbers, say [1, 2], I want to convert this to json format as:
["id_list":
[
{
"id" : 1
},
{
"id" : 2
}
]
]
Can this be done in Swift?
You can try
struct Root :Codable {
let id_list:[InnerItem]
}
struct InnerItem : Codable {
var id:String
}
//
let arr = [1,2]
let js = Root(id_list:arr.map { InnerItem(id: "\($0)")})
do {
let en = try JSONEncoder().encode(js)
let json = String(data: en, encoding:.utf8)!
print(json)
}
catch {
print(error)
}
//
Try this
let dic = ["id_list":[["id": "1"], ["id": "2"], ["id": "3"]]]
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(dic) {
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
}
your output will be like this
{"id_list":[{"id":"1"},{"id":"2"},{"id":"3"}]}
you may try following simple method with for each loop for this:
let arr = [1,2,3,4]
var str = "[\"id_list\":["
for element in arr {
str = str + "{\"id\": \(element)},"
}
str.remove(at: str.index(before: str.endIndex))
str = str + "]]";
OUTPUT
["id_list":[{"id": 1},{"id": 2},{"id": 3},{"id": 4}]]
Create a simple struct adopting Codable
struct Ident : Codable {
let id : Int
}
Map the array to struct items
let array = [1, 2, 3]
let idArray = array.map{ Ident(id:$0) }
Then use JSONEncoder to create the JSON
do {
let jsonData = try JSONEncoder().encode(["id_list" : idArray])
let jsonString = String(data: jsonData, encoding:.utf8)!
print(jsonString) // {"id_list":[{"id":1},{"id":2},{"id":3}]}
} catch { print(error) }
Your JSON format is invalid, it's either {"id_list": ... } or [{"id_list": ... }] but it cannot be ["id_list": ... ]

Extracting data from JSON array with swift Codable

I have a JSON response like this:
I have currently designed my decodable struct to be as follows:
struct PortfolioResponseModel: Decodable {
var dataset: Dataset
struct Dataset: Decodable {
var data: Array<PortfolioData> //I cannot use [Any] here...
struct PortfolioData: Decodable {
//how to extract this data ?
}
}
}
The question is, how do I extract the data inside the array, which can have a value Double or String.
Here is the sample string to make this work on playground:
let myJSONArray =
"""
{
"dataset": {
"data": [
[
"2018-01-19",
181.29
],
[
"2018-01-18",
179.8
],
[
"2018-01-17",
177.6
],
[
"2018-01-16",
178.39
]
]
}
}
"""
Extracting the data:
do {
let details2: PortfolioResponseModel = try JSONDecoder().decode(PortfolioResponseModel.self, from: myJSONArray.data(using: .utf8)!)
//print(details2)
//print(details2.dataset.data[0]) //somehow get "2018-01-19"
} catch {
print(error)
}
I cannot use [Any] here.
Never use Any when decoding JSON because usually you do know the type of the contents.
To decode an array you have to use an unkeyedContainer and decode the values in series
struct PortfolioResponseModel: Decodable {
var dataset: Dataset
struct Dataset: Decodable {
var data: [PortfolioData]
struct PortfolioData: Decodable {
let date : String
let value : Double
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
date = try container.decode(String.self)
value = try container.decode(Double.self)
}
}
}
}
You can even decode the date strings as Date
struct PortfolioData: Decodable {
let date : Date
let value : Double
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
date = try container.decode(Date.self)
value = try container.decode(Double.self)
}
}
if you add a date formatter to the decoder
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
let details2 = try decoder.decode(PortfolioResponseModel.self, from: Data(myJSONArray.utf8))
To add to this, there is a very good example of complex JSON parsing with arrays in particular here. I hope this helps those who are trying to use Codeable with larger, more realistic JSON data.
The overview is this: Imagine you had the following JSON format:
{
"meta": {
"page": 1,
"total_pages": 4,
"per_page": 10,
"total_records": 38
},
"breweries": [
{
"id": 1234,
"name": "Saint Arnold"
},
{
"id": 52892,
"name": "Buffalo Bayou"
}
]
}
This is a common format with the array nested inside. You could create a struct that encapsulates the entire response, accommodating arrays for the "breweries" key, similar to what you were asking above:
struct PagedBreweries : Codable {
struct Meta : Codable {
let page: Int
let totalPages: Int
let perPage: Int
let totalRecords: Int
enum CodingKeys : String, CodingKey {
case page
case totalPages = "total_pages"
case perPage = "per_page"
case totalRecords = "total_records"
}
}
struct Brewery : Codable {
let id: Int
let name: String
}
let meta: Meta
let breweries: [Brewery]
}

Object At Index of Array with Value For Key

Does anyone know how to get the object at the index of an array, at the value for a specific key, and set it to a string?
I used to do this in Objective-C, but I can't quite figure out how to do it in Swift:
NSString *rT = [[self.rA objectAtIndex:0] valueForKey:#"Value"];
I've tried different things like this, but they don't work:
if let JSON = response.result.value {
print("JSON: \(JSON)")
var name: String? = self.rA[0].valueForKey("item_1") as? String
}
Endpoint:
[
{
"item_1": "Austin",
"item_2": "Texas"
}
]
Logging:
2016-04-01 13:35:42.787 A[66185:7391524] Response Array: (
{
"item_1" = Austin;
"item_2" = Texas;
}
)
I assume that you have something like array of dictionaries, So I made some test here it is sample code :
UPDATE
let jsonString = "[ { \"item_1\": \"Austin\", \"item_2\": \"Texas\" }, { \"item_3\": \"Austin3\", \"item_4\": \"Texas4\" } ]"
var array = []
do {
array = try NSJSONSerialization.JSONObjectWithData(jsonString.dataUsingEncoding(NSUTF8StringEncoding)!, options:.AllowFragments) as! NSArray
} catch {
print(error)
}
if let unwrappedValue = (array[0]["item_1"]){
print(unwrappedValue)
}
It will return an optional so it would be nice to use if let statement to unwrap value
For your usage this code should be like this :
if let JSON = response.result.value {
if let unwrappedValue = (JSON[0]["item_1"]){
print(unwrappedValue)
}
}

(Swift) How to parse JSON with struct?

I want to parse JSON with struct and name it.
here is the JSON data:
{
"sgList": [
{
"ID": 11113,
"Name": "soss",
"Price": "10.0000",
"BigImagesUrl": "http://192.165.1.19:886/img/1/2015/7/11/20157111429315728.png",
"SmallImagesUrl": "http://192.165.1.19:886/img/1/2015/7/11/20157111429315728.png"
},
{
"ID": 11958,
"Name": "1017p-02",
"Price": "0.0000",
"BigImagesUrl": "http://192.165.1.13:886/img/rar-upload/f82f22ce-4a33-4ba2-a31d-4bae473f5d48/pics/797_1.jpg",
"SmallImagesUrl": "http://192.165.1.13:886/img/rar-upload/f82f22ce-4a33-4ba2-a31d-4bae473f5d48/pics/797_1-[135-135].jpg"
}
]
}
I spend hours on it and get nothing!
Please help me, Thank you very much!
If you don't want an third party library and do it yourself, it's pretty easy.
Assuming that your JSON String is in a variable called jsonString
let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
Then you can access your data via subsript. For example if you want the Name of the second object in sgList
json["sgList"][1]["Name"]
You can do something like this using SwiftyJSON:
import SwiftyJSON
struct SqList {
let sqList: Array<SqElement>
init(json: JSON) {
let sqArray = json["sqList"].arrayValue.flatMap { SqElement(json: $0) }
self.sqList = sqArray
}
}
struct SqElement {
let id: String
let name: String
let price: String
let bigImagesUrl: String
let smallImagesUrl: String
init?(json: JSON) {
guard
let id = json["ID"].string,
let name = json["Name"].string,
let price = json["Price"].string,
let bigImagesUrl = json["BigImagesUrl"].string,
let smallImagesUrl = json["SmallImagesUrl"].string
else { return nil }
self.id = id
self.name = name
self.price = price
self.bigImagesUrl = bigImagesUrl
self.smallImagesUrl = smallImagesUrl
}
}
In code you just call:
let sqList = SqList(json: JSON(data: dataWithJSON))

How to serialize or convert Swift objects to JSON?

This below class
class User: NSManagedObject {
#NSManaged var id: Int
#NSManaged var name: String
}
Needs to be converted to
{
"id" : 98,
"name" : "Jon Doe"
}
I tried manually passing the object to a function which sets the variables into a dictionary and returns the dictionary. But I would want a better way to accomplish this.
In Swift 4, you can inherit from the Codable type.
struct Dog: Codable {
var name: String
var owner: String
}
// Encode
let dog = Dog(name: "Rex", owner: "Etgar")
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(dog)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
// Decode
let jsonDecoder = JSONDecoder()
let secondDog = try jsonDecoder.decode(Dog.self, from: jsonData)
Along with Swift 4 (Foundation) now it is natively supported in both ways, JSON string to an object - an object to JSON string.
Please see Apple's documentation here JSONDecoder() and here JSONEncoder()
JSON String to Object
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let myStruct = try! decoder.decode(myStruct.self, from: jsonData)
Swift Object to JSONString
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(myStruct)
print(String(data: data, encoding: .utf8)!)
You can find all details and examples here Ultimate Guide to JSON Parsing With Swift 4
UPDATE: Codable protocol introduced in Swift 4 should be sufficient for most of the JSON parsing cases. Below answer is for people who are stuck in previous versions of Swift and for legacy reasons
EVReflection :
This works of reflection principle. This takes less code and also supports NSDictionary, NSCoding, Printable, Hashable and Equatable
Example:
class User: EVObject { # extend EVObject method for the class
var id: Int = 0
var name: String = ""
var friends: [User]? = []
}
# use like below
let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
let user = User(json: json)
ObjectMapper :
Another way is by using ObjectMapper. This gives more control but also takes a lot more code.
Example:
class User: Mappable { # extend Mappable method for the class
var id: Int?
var name: String?
required init?(_ map: Map) {
}
func mapping(map: Map) { # write mapping code
name <- map["name"]
id <- map["id"]
}
}
# use like below
let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
let user = Mapper<User>().map(json)
I worked a bit on a smaller solution that doesn't require inheritance. But it hasn't been tested much. It's pretty ugly atm.
https://github.com/peheje/JsonSerializerSwift
You can pass it into a playground to test it. E.g. following class structure:
//Test nonsense data
class Nutrient {
var name = "VitaminD"
var amountUg = 4.2
var intArray = [1, 5, 9]
var stringArray = ["nutrients", "are", "important"]
}
class Fruit {
var name: String = "Apple"
var color: String? = nil
var weight: Double = 2.1
var diameter: Float = 4.3
var radius: Double? = nil
var isDelicious: Bool = true
var isRound: Bool? = nil
var nullString: String? = nil
var date = NSDate()
var optionalIntArray: Array<Int?> = [1, 5, 3, 4, nil, 6]
var doubleArray: Array<Double?> = [nil, 2.2, 3.3, 4.4]
var stringArray: Array<String> = ["one", "two", "three", "four"]
var optionalArray: Array<Int> = [2, 4, 1]
var nutrient = Nutrient()
}
var fruit = Fruit()
var json = JSONSerializer.toJson(fruit)
print(json)
prints
{"name": "Apple", "color": null, "weight": 2.1, "diameter": 4.3, "radius": null, "isDelicious": true, "isRound": null, "nullString": null, "date": "2015-06-19 22:39:20 +0000", "optionalIntArray": [1, 5, 3, 4, null, 6], "doubleArray": [null, 2.2, 3.3, 4.4], "stringArray": ["one", "two", "three", "four"], "optionalArray": [2, 4, 1], "nutrient": {"name": "VitaminD", "amountUg": 4.2, "intArray": [1, 5, 9], "stringArray": ["nutrients", "are", "important"]}}
This is not a perfect/automatic solution but I believe this is the idiomatic and native way to do such. This way you don't need any libraries or such.
Create an protocol such as:
/// A generic protocol for creating objects which can be converted to JSON
protocol JSONSerializable {
private var dict: [String: Any] { get }
}
extension JSONSerializable {
/// Converts a JSONSerializable conforming class to a JSON object.
func json() rethrows -> Data {
try JSONSerialization.data(withJSONObject: self.dict, options: nil)
}
}
Then implement it in your class such as:
class User: JSONSerializable {
var id: Int
var name: String
var dict { return ["id": self.id, "name": self.name] }
}
Now:
let user = User(...)
let json = user.json()
Note: if you want json as a string, it is very simply to convert to a string: String(data: json, encoding .utf8)
Some of the above answers are completely fine, but I added an extension here, just to make it much more readable and usable.
extension Encodable {
var convertToString: String? {
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
do {
let jsonData = try jsonEncoder.encode(self)
return String(data: jsonData, encoding: .utf8)
} catch {
return nil
}
}
}
struct User: Codable {
var id: Int
var name: String
}
let user = User(id: 1, name: "name")
print(user.convertToString!)
//This will print like the following:
{
"id" : 1,
"name" : "name"
}
Not sure if lib/framework exists, but if you would like to do it automatically and you would like to avoid manual labour :-) stick with MirrorType ...
class U {
var id: Int
var name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
extension U {
func JSONDictionary() -> Dictionary<String, Any> {
var dict = Dictionary<String, Any>()
let mirror = reflect(self)
var i: Int
for i = 0 ; i < mirror.count ; i++ {
let (childName, childMirror) = mirror[i]
// Just an example how to check type
if childMirror.valueType is String.Type {
dict[childName] = childMirror.value
} else if childMirror.valueType is Int.Type {
// Convert to NSNumber for example
dict[childName] = childMirror.value
}
}
return dict
}
}
Take it as a rough example, lacks proper conversion support, lacks recursion, ... It's just MirrorType demonstration ...
P.S. Here it's done in U, but you're going to enhance NSManagedObject and then you'll be able to convert all NSManagedObject subclasses. No need to implement this in all subclasses/managed objects.
struct User:Codable{
var id:String?
var name:String?
init(_ id:String,_ name:String){
self.id = id
self.name = name
}
}
Now just make your object like this
let user = User("1","pawan")
do{
let userJson = try JSONEncoder().encode(parentMessage)
}catch{
fatalError("Unable To Convert in Json")
}
Then reconvert from json to Object
let jsonDecoder = JSONDecoder()
do{
let convertedUser = try jsonDecoder.decode(User.self, from: userJson.data(using: .utf8)!)
}catch{
}
2021 | SWIFT 5.1 | Results solution
Input data:
struct ConfigCreds: Codable {
// some params
}
usage:
// get JSON from Object
configCreds
.asJson()
.onSuccess{ varToSaveJson = $0 }
.onFailure{ _ in // any failure code }
// get object of type "ConfigCreds" from JSON
someJsonString
.decodeFromJson(type: ConfigCreds.self)
.onSuccess { configCreds = $0 }
.onFailure{ _ in // any failure code }
Back code:
#available(macOS 10.15, *)
public extension Encodable {
func asJson() -> Result<String, Error>{
JSONEncoder()
.try(self)
.flatMap{ $0.asString() }
}
}
public extension String {
func decodeFromJson<T>(type: T.Type) -> Result<T, Error> where T: Decodable {
self.asData()
.flatMap { JSONDecoder().try(type, from: $0) }
}
}
///////////////////////////////
/// HELPERS
//////////////////////////////
#available(macOS 10.15, *)
fileprivate extension JSONEncoder {
func `try`<T : Encodable>(_ value: T) -> Result<Output, Error> {
do {
return .success(try self.encode(value))
} catch {
return .failure(error)
}
}
}
fileprivate extension JSONDecoder {
func `try`<T: Decodable>(_ t: T.Type, from data: Data) -> Result<T,Error> {
do {
return .success(try self.decode(t, from: data))
} catch {
return .failure(error)
}
}
}
fileprivate extension String {
func asData() -> Result<Data, Error> {
if let data = self.data(using: .utf8) {
return .success(data)
} else {
return .failure(WTF("can't convert string to data: \(self)"))
}
}
}
fileprivate extension Data {
func asString() -> Result<String, Error> {
if let str = String(data: self, encoding: .utf8) {
return .success(str)
} else {
return .failure(WTF("can't convert Data to string"))
}
}
}
fileprivate func WTF(_ msg: String, code: Int = 0) -> Error {
NSError(code: code, message: msg)
}