11

I need to "replicate" an entiry which is returned from a remote web API service in JSON. It looks like this:

{
  "field1": "some_id",
  "entity_name" = "Entity1"
  "field2": "some name",
  "details1": [{
    "field1": 11,
    "field2": "some value",
    "data": {
      "key1": "value1",
      "key2": "value2",
      "key3": "value3",
      // any other, unknown at compile time keys
    }
  }],
  "details2": {
    "field1": 13,
    "field2": "some value2"
  }
}

Here's my attempt:

struct Entity1 {
  struct Details1 {
    let field1: UInt32
    let field2: String
    let data: [String: String]
  }

  struct Details2 {
    let field1: UInt32
    let field2: String
  }

  let field1: String
  static let entityName = "Entity1"
  let field2: String
  let details1: [Details1]
  let details2: Details2 
}
  1. Is it a good idea to use structs instead of classes for such a goal as mine?
  2. Can I anyhow define a nested struct or a class, say Details1 and create a variable of it at the same time?

Like this:

//doesn't compile 
struct Entity1 {
  let details1: [Details1 { 
  let field1: UInt32
  let field2: String
  let data: [String: String]
}]

6 Answers6

21

You can use any if the following good open-source libraries available to handle the mapping of JSON to Object in Swift, take a look :

Each one have nice a good tutorial for beginners.

Regarding the theme of struct or class, you can consider the following text from The Swift Programming Language documentation:

Structure instances are always passed by value, and class instances are always passed by reference. This means that they are suited to different kinds of tasks. As you consider the data constructs and functionality that you need for a project, decide whether each data construct should be defined as a class or as a structure.

As a general guideline, consider creating a structure when one or more of these conditions apply:

  • The structure’s primary purpose is to encapsulate a few relatively simple data values.
  • It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure.
  • Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
  • The structure does not need to inherit properties or behavior from another existing type.

Examples of good candidates for structures include:

  • The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.
  • A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.
  • A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.

In all other cases, define a class, and create instances of that class to be managed and passed by reference. In practice, this means that most custom data constructs should be classes, not structures.

I hope this help you.

Victor Sigler
  • 23,243
  • 14
  • 88
  • 105
5

HandyJSON is exactly what you need. See code example:

struct Animal: HandyJSON {
    var name: String?
    var id: String?
    var num: Int?
}

let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}"

if let animal = JSONDeserializer.deserializeFrom(json: jsonString) {
    print(animal)
}

https://github.com/alibaba/handyjson

henshao
  • 371
  • 1
  • 4
  • 6
5

Details

  • Xcode 10.2.1 (10E1001), Swift 5

Links

Pods:

More info:

Task

Get itunes search results using iTunes Search API with simple request https://itunes.apple.com/search?term=jack+johnson

Full sample

import UIKit
import Alamofire

// Itunce api doc: https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/#searching

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        loadData()
    }
    
    private func loadData() {
        let urlString = "https://itunes.apple.com/search?term=jack+johnson"
        Alamofire.request(urlString).response { response in
            guard let data = response.data else { return }
            do {
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                let result = try decoder.decode(ItunceItems.self, from: data)
                print(result)
            } catch let error {
                print("\(error.localizedDescription)")
            }
        }
    }
}

struct ItunceItems: Codable {
    let resultCount: Int
    let results: [ItunceItem]
}

struct ItunceItem: Codable {
    var wrapperType: String?
    var artistId: Int?
    var trackName: String?
    var trackPrice: Double?
    var currency: String?
}
Community
  • 1
  • 1
Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • a very detailed answer :) on the other note, how do you use these model objects to update the UIViews? also do you manipulate the data inside the model object and pass it to the View Controller? – J. Goce Feb 06 '17 at 07:01
  • I use viper architecture pattern (protocol oriented programming). So I have data layer, where I prepare, load and save data. Then I inject (read about dependency injection and pod typhoon) this object into view controller and full views. Every view has only properties and visual functions (change color, init corners..). That's all) – Vasily Bodnarchuk Feb 06 '17 at 07:08
0

you could use SwiftyJson and let json = JSONValue(dataFromNetworking) if let userName = json[0]["user"]["name"].string{ //Now you got your value }

bLacK hoLE
  • 781
  • 1
  • 7
  • 20
0

Take a look at this awesome library that perfectly fits your need, Argo on GitHub.

In your case, a struct is ok. You can read more on how to choose between a struct and a class here.

Community
  • 1
  • 1
sweepy_
  • 1,333
  • 1
  • 9
  • 28
0

You can go with this extension for Alamofire https://github.com/sua8051/AlamofireMapper

Declare a class or struct:

class UserResponse: Decodable {
    var page: Int!
    var per_page: Int!
    var total: Int!
    var total_pages: Int!

    var data: [User]?
}

class User: Decodable {
    var id: Double!
    var first_name: String!
    var last_name: String!
    var avatar: String!
}

Use:

import Alamofire
import AlamofireMapper

let url1 = "https://raw.githubusercontent.com/sua8051/AlamofireMapper/master/user1.json"
        Alamofire.request(url1, method: .get
            , parameters: nil, encoding: URLEncoding.default, headers: nil).responseObject { (response: DataResponse<UserResponse>) in
                switch response.result {
                case let .success(data):
                    dump(data)
                case let .failure(error):
                    dump(error)
                }
        }
Sua Le
  • 137
  • 6