ObjectMapper 如何基于 JSON 映射不同的对象

Posted

技术标签:

【中文标题】ObjectMapper 如何基于 JSON 映射不同的对象【英文标题】:ObjectMapper how to map different object based on JSON 【发布时间】:2017-09-02 07:49:53 【问题描述】:

我正在使用 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?非常感谢!

【问题讨论】:

你想通过区分它们来达到什么目的? 每个CatFish 对象都有我想稍后调用的独特函数。 【参考方案1】:

详情

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 示例

检测数组中的对象

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  $0.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
        
    

映射类

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"]
    

帮手

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

用法(从文件中读取json)

    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: $0))" .joined(separator: ", ") )
                        // class: Cat, class: Fish, class: Animal
                        print("===============\n\(data)")
                    
                
            catch 
                print("\(error.localizedDescription)")
            
        
    

可编码示例

检测数组中的对象

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
            
        
    

映射类

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)
    

帮手

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"
    

用法(从文件中读取json)

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: $0))"  )
            //print("===============")
            //print(result.format(options: .prettyPrinted))
         catch let error 
            print("\(error.localizedDescription)")
        
    


输出

[“猫”、“鱼”、“动物”、“猫”]

【讨论】:

是的,这正是我最终不得不做的。感谢您的回答! 你好@VasilyBodnarchuk 我用了你的答案,但我被困在一个地方,我问了这个问题你能帮我吗?这是我的问题的链接***.com/questions/54782782/…【参考方案2】:

我的解决方案是使用 ObjectMapper 库来实现 swift。

如果你有这个:


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

本网站返回您的完整对象

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/

【讨论】:

干得好安德烈...继续努力!那个现场站点对我将 json 对象转换为 objectmapper 帮助很大【参考方案3】:

首先将您的 JSON 字符串转换为数组,然后遍历数组中的每个字典,检查类型值,然后为 public func map(JSON: [String: Any]) -&gt; N? 相应地选择模型

【讨论】:

我认为这不会是有效的。但选择有限。【参考方案4】:
        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)
    

【讨论】:

以上是关于ObjectMapper 如何基于 JSON 映射不同的对象的主要内容,如果未能解决你的问题,请参考以下文章

swift Demo使用ObjectMapper映射json

使用 ObjectMapper 映射 JSON 对象的通用函数

Swift - 嵌套对象的映射 (Objectmapper)

ObjectMapper的使用

Swift:如何将带有 Alamofilre 或 SwiftyJSON 的 JSON 字符串转换为 ObjectMapper?

jackson中objectMapper的使用