ObjectMapper 如何根据 JSON 映射不同的对象

ObjectMapper how to map different object based on JSON

我正在使用 ObjectMapper (https://github.com/Hearst-DD/ObjectMapper) 将我的 JSON 映射到 Swift 个对象。

假设我有这个 JSON 结构:

{
  animals: [
    {
      "type": "Cat",
      "weight": 23,
      "catchMice": true
    },
    {
      "type": "Fish",
      "weight": 1,
      "swim": true
    }
  ]
}

我有以下 Swift 个对象:

class Foo: Mappable {
  var animals: [Animal] = []

  func mapping(map: Map) {
    animals <- map["animals"] //But I want to be able to distinguish between Cat and Fish objects here
  }
}

class Animal: Mappable {
  var type: String?
  var weight: Double?

  required init?(map: Map) {}

  func mapping(map: Map) {
    type <- map["type"]
    weight <- map["weight"]
  }
}

class Cat: Animal { // How do I make use of this class
  var catchMice: Bool?
}

class Fish: Animal { // How do I make use of this class 
  var swim: Bool?
}

如何使用 JSON 对象中的 type 键区分映射中的 CatFish?非常感谢!

首先将 JSON 字符串转换为数组,然后遍历数组中的每个字典,检查类型值,然后为 public func map(JSON: [String: Any]) -> N?

选择相应的模型

详情

  • Xcode 10.2.1 (10E1001), Swift 5

json 文件

{
    "animals": [
        {
            "id": 1,
            "name": "Cat",
            "type": "cat",
            "weight": 23,
            "area": ["home", "street"],
            "can_climb_trees": true,
            "competence": [
                { "id": 1, "name": "to catch mouse" },
                { "id": 2, "name": "to mew" },
                { "id": 3, "name": "to wake people up in the morning" },
                { "id": 4, "name": "to eat fish" }
            ]
        },
        {
            "id": 2,
            "name": "fish",
            "type": "fish",
            "weight": 1,
            "area": ["ocean", "lake"],
            "can_swim": false,
            "competence": [
                { "id": 5, "name": "to swim" },
                { "id": 6, "name": "to tease a cat" }
            ]
        },
        {
            "id": 3,
            "name": "dog",
            "weight": 55,
            "area": ["house", "street"],
            "competence": [
                { "id": 5, "name": "to bring newspaper" },
                { "id": 6, "name": "to a good booy" }
            ]
        },
        {
            "id": 4,
            "name": "Cat",
            "type": "cat",
            "weight": 23,
            "area": ["home", "street"],
            "can_climb_trees": true,
            "competence": [
                { "id": 1, "name": "to catch mouse" },
                { "id": 2, "name": "to mew" },
                { "id": 3, "name": "to wake people up in the morning" },
                { "id": 4, "name": "to eat fish" }
            ]
        }
    ]
}

ObjectMapper 示例

Detect objects in array

import Foundation
import ObjectMapper

class AnimalsArrayTransformType: TransformType {

    public typealias Object = [Animal]
    public typealias JSON = [[String: Any]]

    func transformToJSON(_ value: [Animal]?) -> [[String : Any]]? {
        guard let animals = value else { return nil }
        return animals.map { [=11=].toJSON() }
    }

    func transformFromJSON(_ value: Any?) -> [Animal]? {
        guard let animals = value as? [[String: Any]] else { return nil }
        return animals.compactMap { dictionary -> Animal? in
            if let cat = Cat(JSON: dictionary) { return cat }
            if let fish = Fish(JSON: dictionary) { return fish }
            if let animal = Animal(JSON: dictionary) { return animal }
            return nil
        }
    }
}

Mapping classes

import Foundation
import ObjectMapper

class Animals: Mappable, CustomStringConvertible {
    private(set) var animals: [Animal] = []
    required init?(map: Map) { }

    func mapping(map: Map) {
        animals <- (map["animals"], AnimalsArrayTransformType())
    }
}

class BaseObject: Mappable, CustomStringConvertible {
    private(set) var id: Int?
    private(set) var name: String?

    required init?(map: Map) { mapping(map: map) }

    func mapping(map: Map) {
        id <- map["id"]
        name <- map["name"]
    }
}

class Animal: BaseObject {
    private(set) var type: String?
    private(set) var weight: Double?
    private(set) var area: [String]?
    private(set) var competence: [BaseObject]?

    required init?(map: Map) { super.init(map: map) }

    override func mapping(map: Map) {
        super.mapping(map: map)
        type <- map["type"]
        weight <- map["weight"]
        area <- map["area"]
        competence <- map["competence"]
    }
}

class Cat: Animal {
    private(set) var canClimbTrees: Bool?

    required init?(map: Map) {
        super.init(map: map)
        if canClimbTrees == nil { return nil }
    }

    override func mapping(map: Map) {
        super.mapping(map: map)
        canClimbTrees <- map["can_climb_trees"]
    }
}

class Fish: Animal {
    private(set) var canSwim: Bool?

    required init?(map: Map) {
        super.init(map: map)
        if canSwim == nil { return nil }
    }

    override func mapping(map: Map) {
        super.mapping(map: map)
        canSwim <- map["can_swim"]
    }
}

Helpers

extension Mappable {
    var description: String {
        return toJSONString(prettyPrint: true) ?? "\(self)"
    }
}

Usage (read json from file)

    func sample() {
        if let path = Bundle.main.path(forResource: "data", ofType: "json") {
            do {
                let text = try String(contentsOfFile: path, encoding: .utf8)
                if let dict = try JSONSerialization.jsonObject(with: text.data(using: .utf8)!, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any] {
                    if let data = Animals(JSON: dict) {
                        print(data.animals.map {"class: \(type(of: [=14=]))" }.joined(separator: ", ") )
                        // class: Cat, class: Fish, class: Animal
                        print("===============\n\(data)")
                    }
                }
            }catch {
                print("\(error.localizedDescription)")
            }
        }
    }

可编码样本

Detect objects in array

class Animals: Codable {

    fileprivate enum CodingKeys: String, CodingKey {
        case animals
    }

    private(set) var animals: [Animal]

    required init(from decoder: Decoder) throws {
        self.animals = []
        let container = try decoder.container(keyedBy: CodingKeys.self)
        var unkeyedDecodingContainer = try container.nestedUnkeyedContainer(forKey: .animals)
        while !unkeyedDecodingContainer.isAtEnd {
            if let obj = try? unkeyedDecodingContainer.decode(Cat.self) {
                animals.append(obj)
                continue
            }

            if let obj = try? unkeyedDecodingContainer.decode(Fish.self) {
                animals.append(obj)
                continue
            }

            if let obj = try? unkeyedDecodingContainer.decode(Animal.self) {
                animals.append(obj)
                continue
            }
        }
    }
}

Mapping classes

enum AnimalType: String, Codable {
    case cat = "cat", fish = "fish"
}

class BaseObject: Codable {
    private(set) var id: Int?
    private(set) var name: String?
}

class Animal: BaseObject {
    private(set) var type: AnimalType?
    private(set) var weight: Int?
    private(set) var area: [String]?
    private(set) var competence: [BaseObject]?

    private enum CodingKeys: String, CodingKey {
        case type, weight, area, competence
    }

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encodeIfPresent(type, forKey: .type)
        try container.encodeIfPresent(weight, forKey: .weight)
        try container.encodeIfPresent(area, forKey: .area)
        try container.encodeIfPresent(competence, forKey: .competence)
        try super.encode(to: encoder)
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        type = try container.decodeIfPresent(AnimalType.self, forKey: .type)
        weight = try container.decodeIfPresent(Int.self, forKey: .weight)
        area = try container.decodeIfPresent([String].self, forKey:  .area)
        competence = try container.decodeIfPresent([BaseObject].self, forKey: .competence)
        try super.init(from: decoder)
    }
}

class Cat: Animal {
    private(set) var canClimbTrees: Bool

    private enum CodingKeys: String, CodingKey {
        case canClimbTrees = "can_climb_trees"
    }

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encodeIfPresent(canClimbTrees, forKey: .canClimbTrees)
        try super.encode(to: encoder)
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.canClimbTrees = try container.decode(Bool.self, forKey: .canClimbTrees)
        try super.init(from: decoder)
    }
}


class Fish: Animal {

    private(set) var canSwim: Bool

    enum CodingKeys: String, CaseIterable, CodingKey {
        case canSwim = "can_swim"
    }

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encodeIfPresent(canSwim, forKey: .canSwim)
        try super.encode(to: encoder)
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.canSwim = try container.decode(Bool.self, forKey: .canSwim)
        try super.init(from: decoder)
    }
}

Helpers

extension Decodable where Self : Encodable {

    dynamic func format(options: JSONEncoder.OutputFormatting) -> String {
        let encoder = JSONEncoder()
        encoder.outputFormatting = options
        do {
            let jsonData = try encoder.encode(self)
            if let jsonString = String(data: jsonData, encoding: .utf8) { return "\(jsonString)" }
        } catch {
            print("\(error.localizedDescription)")
        }
        return "nil"
    }
}

Usage (read json from file)

func sample() {
    if let path = Bundle.main.path(forResource: "data", ofType: "json") {
        do {
            guard let data = try String(contentsOfFile: path, encoding: .utf8).data(using: .utf8) else { return }
            let decoder = JSONDecoder()
            let result = try decoder.decode(Animals.self, from: data)
            print(result.animals.map {"\(type(of: [=18=]))" } )
            //print("===============")
            //print(result.format(options: .prettyPrinted))
        } catch let error {
            print("\(error.localizedDescription)")
        }
    }
}

输出

["Cat", "Fish", "Animal", "Cat"]

我的解决方案是将 ObjectMapper 库用于 swift。

如果你有这个:

{
    "animals": [{
            "type": "Cat",
            "weight": 23,
            "catchMice": true
        },
        {
            "type": "Fish",
            "weight": 1,
            "swim": true
        }
    ]
}

本站returns您的完整对象

import Foundation 
import ObjectMapper 

class Main: Mappable { 

    var animals: [Animals]? 

    required init?(map: Map){ 
    } 

    func mapping(map: Map) {
        animals <- map["animals"] 
    }
} 

class Animals: Mappable { 

    var type: String? 
    var weight: NSNumber? 
    var catchMice: Bool? 

    required init?(map: Map){ 
    } 

    func mapping(map: Map) {
        type <- map["type"] 
        weight <- map["weight"] 
        catchMice <- map["catchMice"] 
    }
} 

我正在从事这个项目,它有助于为 'ObjectMappable' 库 (swift) 创建可映射对象。

https://github.com/andreycattalin/JSONtoSwiftObjectMapper

现场版:http://izee.ro/andrey/JSONtoSwiftObjectMapper/

        Simple example as follow
        //
        //  ViewController.swift
        //  TriyalJSON
        //
        //  Created by Mac on 19/02/19.
        //  Copyright © 2019 shital. All rights reserved.
        //

        import UIKit
        class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
            @IBOutlet weak var mytable: UITableView!
            var arrmain = [GETArrayData]()
            override func viewDidLoad() {
                super.viewDidLoad()
                getdata()
                // Do any additional setup after loading the view, typically from a nib.
            }

            override func didReceiveMemoryWarning() {
                super.didReceiveMemoryWarning()
                // Dispose of any resources that can be recreated.
            }
        func getdata()
        {
            let dict = [
                "name":"shital",
                "address":"pune"
            ]
            APIManager.sharedInstance.getdata(parms: dict, onsuccess: { (responsedata, anystring) in
                print(responsedata,anystring)
                self.arrmain = responsedata
                let str = self.arrmain[0]
                print("Email-",str.email)
                self.mytable.reloadData()
            }) { (error1, error2) in
                print(error1,error2)
            }
            }
            func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                return self.arrmain.count
            }
            func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                        let cell:UITableViewCell = mytable.dequeueReusableCell(withIdentifier: "TableViewCell") as! UITableViewCell
                let str1 = self.arrmain[indexPath.row]
                cell.textLabel?.text = str1.email

                return cell

            }
            func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
                return 400.0
            }
            func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
                let obj = storyboard?.instantiateViewController(withIdentifier: "ViewControllerFrist") as! ViewControllerFrist
        //        ViewControllerFrist.arrpass = self.arrmain
                var strpass = self.arrmain[indexPath.row]
                obj.arrpass = [strpass]
           // obj.arrpass = self.arrmain

                self.navigationController? .pushViewController(obj, animated: true)
            }
        }
        ------------------------------
        ------------------------------
        ------------------------------
        ------------------------------
        ------------------------------
        ------------------------------
        ------------------------------

        //
        //  ViewControllerFrist.swift
        //  TriyalJSON
        //
        //  Created by Mac on 20/02/19.
        //  Copyright © 2019 shital. All rights reserved.
        //

        import UIKit

        class ViewControllerFrist: UIViewController,UITableViewDelegate,UITableViewDataSource {
            var arrpass = [GETArrayData]()
            static let fristobject = ViewControllerFrist()
            @IBOutlet weak var mytableView: UITableView!
            override func viewDidLoad() {
                super.viewDidLoad()

                // Do any additional setup after loading the view.
            }

            override func didReceiveMemoryWarning() {
                super.didReceiveMemoryWarning()
                // Dispose of any resources that can be recreated.
            }
            func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                print(self.arrpass.count)
                return self.arrpass.count

            }
            func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                let cell:TableViewCell = mytableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
                let str = self.arrpass[indexPath.row]
                cell.textLabel1.text = str.email
                cell.textLabel2.text = "\(str.id)"

                return cell
            }
            func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
                return 300.0
            }

        }

        ------------------------------
        ------------------------------
        ------------------------------
        ------------------------------
        ------------------------------
        ------------------------------
        ------------------------------


        //
        //  APIManager.swift
        //  TriyalJSON
        //
        //  Created by Mac on 19/02/19.
        //  Copyright © 2019 shital. All rights reserved.
        //

        import UIKit
        import Foundation
        import Alamofire
        import ObjectMapper
        class APIManager: NSObject {

            static let sharedInstance = APIManager()

            func getdata(parms:[String:Any], onsuccess:@escaping([GETArrayData],String?) ->Void,onfailure:@escaping(String?,String?) ->Void)
            {
                let url = URL.init(string: "https://jsonplaceholder.typicode.com/comments")

                let headrs : HTTPHeaders = ["content-type":"application/json"]
                Alamofire.request(url!, method: .get, parameters: parms, encoding: JSONEncoding.default, headers: headrs).responseJSON
                    {
                        response in
                        switch response.result{
                        case .success:

        //                    let string = NSString(data: response.data!, encoding: String.Encoding.utf8.rawValue)
        //                    print("string:\(String(describing: string))")
                            do{
                                let jsonResponse = try JSONSerialization.jsonObject(with: response.data!, options: []) as! AnyObject
                                //let userdetails = Mapper<DataClasss>() .mapArray(JSONObject: jsonResponse)
        //                        let userdetalis = Mapper<GETArrayData>() .mapArray(JSONString: jsonResponse)
                                let userdetails = Mapper<GETArrayData>() .mapArray(JSONObject: jsonResponse)
                                print(jsonResponse)
                                if jsonResponse != nil
                                {
                                    onsuccess(userdetails!,"success")
                                }
                                else
                                {
                                    onfailure((response.error?.localizedDescription)!,"fail")
                                }

                            }catch let parsingError{

                                print("error",parsingError)
                                onfailure((response.error?.localizedDescription)!,"fail")
                            }
                            break
                        case.failure(let error):

                            print(error)
                            onfailure((response.error?.localizedDescription)!,"fail")
                        }

                }
            } 


        }


    --------------------
    --------------------
    --------------------
    --------------------
    --------------------
    --------------------
    //
    //  SecendViewController.swift
    //  ITGURUassignment
    //
    //  Created by Mac on 18/02/19.
    //  Copyright © 2019 shital. All rights reserved.
    //

    import UIKit

    class SecendViewController: UIViewController {


        @IBOutlet weak var textName: UITextField!

        @IBOutlet weak var txtEmail: UITextField!

        @IBOutlet weak var txtFeatureTitle: UITextField!

        @IBOutlet weak var txtFeatureDescription: UITextView!


        @IBOutlet weak var txtUseCase: UITextView!

        @IBOutlet weak var btnlow: UIButton!



        var strresult = ""


        @IBAction func sendRequestPressed(_ sender: UIButton) {
           var strname = self.textName.text!
            var stremail = self.txtEmail.text!
            var strtitle = self.txtFeatureTitle.text!

            if strname.count <= 0
           {
            print("Enter Frist Name")
            }

           else if stremail.count <= 0 {
                print("enter last name")

            }
            else if strtitle.count <= 0 {
                print("Enter feature title")

            }
            else if self.strresult.count <= 0
            {
                print("Button  not selected:\(strresult)")
            }
            else
            {
                print("Button  selected:\(strresult)")
                let dict = [
                    "AppID":"67-5555545ete",
                    "FeatureTitle":"\(self.txtFeatureTitle.text!)",
                    "UserName":"laura",
                    "UserEmail":"\(self.txtEmail.text!)",
                    "Priority":self.strresult,
                    "Description":"\(self.txtFeatureDescription.text ?? "")",
                    "UseCase":"\(self.txtUseCase.text ?? "")",
                    "DeviceType":"iphone"
                ]
                print(dict)
            }



        }


        @IBAction func btnhighpressed(_ sender: UIButton) {
            self.strresult = "H"
            print(strresult)
            self.btnhigh.setImage(imgselected, for: .normal)
            self.btnlow.setImage(imgunselected, for: .normal)
            self.btnmedium.setImage(imgunselected, for: .normal)
        }


        @IBAction func btnlowpressed(_ sender: UIButton) {
            self.strresult = "L"
            print(strresult)
            self.btnhigh.setImage(imgunselected, for: .normal)
            self.btnlow.setImage(imgselected, for: .normal)
            self.btnmedium.setImage(imgunselected, for: .normal)
        }


        @IBAction func btnmedium(_ sender: UIButton) {
            self.strresult = "M"
            print(strresult)
            self.btnhigh.setImage(imgunselected, for: .normal)
            self.btnlow.setImage(imgunselected, for: .normal)
            self.btnmedium.setImage(imgselected, for: .normal)
        }


        @IBOutlet weak var btnmedium: UIButton!

        @IBOutlet weak var btnhigh: UIButton!

        let imgselected = UIImage.init(named: "Selected")

        let imgunselected = UIImage.init(named: "Unselected")

        override func viewDidLoad() {
            super.viewDidLoad()

            self.btnhigh.setImage(imgunselected, for: .normal)

            self.btnlow.setImage(imgunselected, for: .normal)

            self.btnmedium.setImage(imgunselected, for: .normal)
            // Do any additional setup after loading the view.
        }

        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }

    }

-----------------
-----------------
-----------------
-----------------
-----------------
----------------
----------------
//
//  ViewController.swift
//  ITGURUassignment
//
//  Created by Mac on 18/02/19.
//  Copyright © 2019 shital. All rights reserved.
//

import UIKit

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {

//    var arrCountryList = [CountryDetails]()
    var arruserdetalis = [USERdetalis]()
//    var usermasterdetails = USERdetalis()

    @IBAction func featureRequest(_ sender: UIButton) {
        let objectforsecviewcontroller = storyboard?.instantiateViewController(withIdentifier: "SecendViewController") as! SecendViewController
        self.navigationController?.pushViewController(objectforsecviewcontroller, animated: true)

    }
    @IBOutlet weak var getdetalitable: UITableView!
    //    @IBAction func nextbtn(_ sender: UIButton) {
//        let vc = storyboard? .instantiateViewController(withIdentifier: "SecendViewController") as! SecendViewController
//        self.navigationController?.pushViewController(vc, animated: true)
//
//    }


    override func viewDidLoad() {
        super.viewDidLoad()
        getdata()

        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
func getdata()
{
    let dict = [
        "name":"shital",
        "roll":"one"
    ]
    APIManager.sharedInstance.getuserdetalis(parms: dict, onsuccess: { (arruserdetalis, anystring) in
        print(arruserdetalis,anystring)
        self.arruserdetalis = arruserdetalis
        var str = arruserdetalis[0]
        self.getdetalitable.reloadData()
    }) { (error1, error2) in
        print(error1,error2)
    }

    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.arruserdetalis.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell:TableViewCell = getdetalitable.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell


        let userdetalis = self.arruserdetalis[indexPath.row]


        cell.textLabel?.text = userdetalis.title ?? ""


        if(userdetalis.isSelected == true)
        {
            cell.btnVote.setTitle("Voted", for: .normal)
            cell.btnVote.isEnabled = false
        }
        else {

            cell.btnVote.setTitle("Vote your vote", for: .normal)
        }

        cell.btnVote.tag = indexPath.row

        cell.btnVote.addTarget(self, action: #selector(voteButtonPressed(sender:)), for: .touchUpInside)


        return cell
    }

    @objc func voteButtonPressed(sender:UIButton){

        let userdetalis = self.arruserdetalis[sender.tag]


        self.getdetalitable.reloadData()


        let dict = [
            "commandtype":"registervote",
            "RequestID":userdetalis.id,
            "usename":userdetalis.title

            ] as [String : Any]

        APIManager.sharedInstance.voteApi(parms: dict, onsuccess: { (response, anystring) in
            print(response,anystring)
            self.getdata()
        }) { (error1, error2) in
            print(error1,error2)
        }



    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 500.0
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {


        let third = storyboard?.instantiateViewController(withIdentifier: "ThirdViewController") as! ThirdViewController
       self.navigationController? .pushViewController(third, animated: true)
    }
}