JSON 解码为变体模型 Swift4

JSON Decoding into variant model Swift4

我有两个 JSON 请求。根据 header 中的 MessageCategory,有一个 header 和一个不同的 body。我如何在一个游乐场中对此进行解码。

第一个游乐场是:

import Cocoa

var str = "SimplePaymentJSON playground"


struct Request :Decodable {
    var SaleToPOIRequest : SaleToPOIRequestStr
}

struct MessageHeaderStr : Decodable {

    var MessageClass : String
    var MessageCategory : String
    var MessageType : String
    var ServiceID : String
    var SaleID : String
    var POIID : String
}

struct PaymentRequestStr :  Decodable {
    var SaleData : SaleData
    var PaymentTransaction : PaymentTransaction
    var PaymentData : PaymentData
}

struct SaleToPOIRequestStr :  Decodable {
    var MessageHeader : MessageHeaderStr
    var PaymentRequest : PaymentRequestStr

}

struct SaleData : Decodable {
    var SaleTransactionID : SaleTransactionID
}

struct PaymentTransaction : Decodable {

    var AmountsReq : AmountsReq
    var TransactionConditions: TransactionConditions

}

struct SaleTransactionID: Decodable {
    var TransactionID : String
    var TimeStamp : String
}

struct AmountsReq: Decodable {
    var Currency : String
    var RequestedAmount : String
}

struct TransactionConditions: Decodable {
    var LoyaltyHandling : String
}

struct PaymentData: Decodable {
    var PaymentType : String
}


let json = """

{
  "SaleToPOIRequest" : {
    "MessageHeader" : {
      "MessageClass": "Service",
      "MessageCategory": "Payment",
      "MessageType": "Request",
      "ServiceID": "642",
      "SaleID": "SaleTermA",
      "POIID": "POITerm1"
    },
    "PaymentRequest": {
      "SaleData": {
        "SaleTransactionID": {
          "TransactionID": "579",
          "TimeStamp": "2009-03-10T23:08:42.4+01:00"
        }
      },
      "PaymentTransaction": {
        "AmountsReq": {
          "Currency": "EUR",
          "RequestedAmount": "104.11"
        },
        "TransactionConditions": { "LoyaltyHandling": "Forbidden" }
      },
      "PaymentData": { "PaymentType": "Normal" }
    }
  }
}

""".data(using: .utf8)!

// let customer = try! JSONDecoder().decode(Customer.self, from: json)
// print(customer)
do {

    let paymentRequest = try JSONDecoder().decode(Request.self, from: json )

    print(paymentRequest)
    print(paymentRequest.SaleToPOIRequest.MessageHeader.MessageType)
    print(paymentRequest.SaleToPOIRequest.PaymentRequest.PaymentTransaction.AmountsReq.Currency)
    print(paymentRequest.SaleToPOIRequest.PaymentRequest.PaymentTransaction.AmountsReq.RequestedAmount)

}

catch let jsonErr {

    print("Error decoding Json", jsonErr)

}

另一个游乐场是:

import Cocoa

var str = "LoginJSON playground"


struct Request : Decodable {
    var SaleToPOIRequest : SaleToPOIRequestStr
}

struct MessageHeaderStr : Decodable {

    var MessageClass : String
    var MessageCategory : String
    var MessageType : String
    var ServiceID : String
    var SaleID : String
    var POIID : String
}

struct LoginRequestStr :  Decodable {
    var OperatorLanguage : String
    var OperatorID : String
    var ShiftNumber : String
    var POISerialNumber : String
    var DateTime : String
    var SaleSoftware : SaleSoftwareStr
    var SaleTerminalData : SaleTerminalDataStr
}

struct SaleToPOIRequestStr :  Decodable {
    var MessageHeader : MessageHeaderStr
    var LoginRequest : LoginRequestStr

}

struct SaleSoftwareStr :  Decodable {
    var ProviderIdentification : String
    var ApplicationName : String
    var SoftwareVersion : String
    var CertificationCode : String
}

struct SaleProfileStr : Decodable {
    var GenericProfile : String
    var ServiceProfiles : String
}

struct SaleTerminalDataStr :  Decodable {

    var TerminalEnvironment : String
    var SaleCapabilities : String
    var SaleProfile : SaleProfileStr
}



let json = """

{
  "SaleToPOIRequest": {
    "MessageHeader": {
      "ProtocolVersion": "3.0",
      "MessageClass": "Service",
      "MessageCategory": "Login",
      "MessageType": "Request",
      "ServiceID": "498",
      "SaleID": "SaleTermA",
      "POIID": "POITerm1"
    },
    "LoginRequest": {
      "OperatorLanguage": "de",
      "OperatorID": "Cashier16",
      "ShiftNumber": "2",
      "POISerialNumber": "78910AA46010005",
      "DateTime": "2015-03-08T09:13:51.0+01:00",
      "SaleSoftware": {
        "ProviderIdentification": "PointOfSaleCo",
        "ApplicationName": "SaleSys",
        "SoftwareVersion": "01.98.01",
        "CertificationCode": "ECTS2PS001"
      },
      "SaleTerminalData": {
        "TerminalEnvironment": "Attended",
        "SaleCapabilities": "PrinterReceipt CashierStatus CashierError CashierDisplay CashierInput",
        "SaleProfile": {
          "GenericProfile": "Extended",
          "ServiceProfiles": "Loyalty PIN CardReader"
        }
      }
    }
  }
}

""".data(using: .utf8)!

// let customer = try! JSONDecoder().decode(Customer.self, from: json)
// print(customer)
do {

    let loginRequest = try JSONDecoder().decode(Request.self, from: json )

    print(loginRequest)
    print(loginRequest.SaleToPOIRequest.MessageHeader.ServiceID)
    print(loginRequest.SaleToPOIRequest.LoginRequest.DateTime)
    print(loginRequest.SaleToPOIRequest.LoginRequest.SaleSoftware.CertificationCode)
    print(loginRequest.SaleToPOIRequest.LoginRequest.SaleTerminalData.SaleProfile.GenericProfile)

}

catch let jsonErr {

    print("Error decoding Json", jsonErr)

}

如何根据 MessageHeader 中的 MessageCategory 进行解码?

有什么link联合吗?

亲切的问候

据我所见,SaleToPOIRequest 包含 PaymentRequestStrLoginRequestStr 类型的 属性。我建议您使用关联值声明 enum 以存储 PaymentRequestStrLoginRequestStr 的实例。检查此代码段:

enum LoginOrPaymentRequest: Decodable {

    case login(LoginRequestStr)
    case payment(PaymentRequestStr)
    case unknown

    private enum CodingKeys: String, CodingKey {

        case LoginRequest
        case PaymentRequest
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let loginRequest = try container.decodeIfPresent(LoginRequestStr.self, forKey: .LoginRequest) {
            self = .login(loginRequest)
        } else if let paymentRequest = try container.decodeIfPresent(PaymentRequestStr.self, forKey: .PaymentRequest) {
            self = .payment(paymentRequest)
        } else {
            self = .unknown
        }
    }
}

同时更改 SaleToPOIRequest 的实现,添加类型 LoginOrPaymentRequest:

的 属性
struct SaleToPOIRequest: Decodable {

    let messageHeader: MessageHeaderStr?
    let loginOrPaymentRequest: LoginOrPaymentRequest

    private enum CodingKeys: String, CodingKey {

        case MessageHeader
    }

    private init(messageHeader: MessageHeaderStr?,
                 loginOrPaymentRequest: LoginOrPaymentRequest) {
        self.messageHeader = messageHeader
        self.loginOrPaymentRequest = loginOrPaymentRequest
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.init(messageHeader: try container.decode(MessageHeaderStr.self, forKey: .MessageHeader),
                  loginOrPaymentRequest: try LoginOrPaymentRequest(from: decoder))
    }
}

此结构包含有关 SaleToPOIRequest:

实例的信息
struct DataResponse: Decodable {

    let saleToPOIRequest: SaleToPOIRequest?

    private enum CodingKeys: String, CodingKey {
        case SaleToPOIRequest
    }

    private init(saleToPOIRequest: SaleToPOIRequest?) {
        self.saleToPOIRequest = saleToPOIRequest
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.init(saleToPOIRequest: try container.decode(SaleToPOIRequest.self, forKey: .SaleToPOIRequest))
    }
}

如何测试:

let dataResponse = try JSONDecoder().decode(DataResponse.self, from: json)
print(dataResponse.saleToPOIRequest?.messageHeader?.POIID)
if let loginOrPaymentRequest = dataResponse.saleToPOIRequest?.loginOrPaymentRequest {
    if case .login(let loginRequest) = loginOrPaymentRequest {
        print(loginRequest.OperatorLanguage)
    } else if case .payment(let paymentRequest) = loginOrPaymentRequest {
        print(paymentRequest.PaymentData.PaymentType)
    }
}

我记得你提到过你需要检查MessageCategory的值。在这种情况下,将此函数添加到 LoginOrPaymentRequest:

init(from decoder: Decoder, messageCategory: String) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    if let loginRequest = try container.decodeIfPresent(LoginRequestStr.self, forKey: .LoginRequest), messageCategory == "Login" {
        self = .login(loginRequest)
    } else if let paymentRequest = try container.decodeIfPresent(PaymentRequestStr.self, forKey: .PaymentRequest), messageCategory == "Payment" {
        self = .payment(paymentRequest)
    } else {
        self = .unknown
    }
}

并修改SaleToPOIRequestinit

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let messageHeader = try container.decode(MessageHeaderStr.self, forKey: .MessageHeader)
    let loginOrPaymentRequest = try LoginOrPaymentRequest(from: decoder,
                                                          messageCategory: messageHeader.MessageCategory)
    self.init(messageHeader: messageHeader,
              loginOrPaymentRequest: loginOrPaymentRequest)
}

所以最后看起来像这样:

import Cocoa
//
// 
//

var str = "LoginJSON or PaymentJSON playground"


let json1 = """

{
  "SaleToPOIRequest": {
    "MessageHeader": {
      "ProtocolVersion": "3.0",
      "MessageClass": "Service",
      "MessageCategory": "Login",
      "MessageType": "Request",
      "ServiceID": "498",
      "SaleID": "SaleTermA",
      "POIID": "POITerm1"
    },
    "LoginRequest": {
      "OperatorLanguage": "de",
      "OperatorID": "Cashier16",
      "ShiftNumber": "2",
      "POISerialNumber": "78910AA46010005",
      "DateTime": "2015-03-08T09:13:51.0+01:00",
      "SaleSoftware": {
        "ProviderIdentification": "PointOfSaleCo",
        "ApplicationName": "SaleSys",
        "SoftwareVersion": "01.98.01",
        "CertificationCode": "ECTS2PS001"
      },
      "SaleTerminalData": {
        "TerminalEnvironment": "Attended",
        "SaleCapabilities": "PrinterReceipt CashierStatus CashierError CashierDisplay CashierInput",
        "SaleProfile": {
          "GenericProfile": "Extended",
          "ServiceProfiles": "Loyalty PIN CardReader"
        }
      }
    }
  }
}

""".data(using: .utf8)!


let json2 = """

{
  "SaleToPOIRequest" : {
    "MessageHeader" : {
      "MessageClass": "Service",
      "MessageCategory": "Payment",
      "MessageType": "Request",
      "ServiceID": "642",
      "SaleID": "SaleTermA",
      "POIID": "POITerm1"
    },
    "PaymentRequest": {
      "SaleData": {
        "SaleTransactionID": {
          "TransactionID": "579",
          "TimeStamp": "2009-03-10T23:08:42.4+01:00"
        }
      },
      "PaymentTransaction": {
        "AmountsReq": {
          "Currency": "EUR",
          "RequestedAmount": "104.11"
        },
        "TransactionConditions": { "LoyaltyHandling": "Forbidden" }
      },
      "PaymentData": { "PaymentType": "Normal" }
    }
  }
}

""".data(using: .utf8)!


// ------------------- LoginRequest Body -------------------


struct LoginRequest :  Decodable {
    var OperatorLanguage : String
    var OperatorID : String
    var ShiftNumber : String
    var POISerialNumber : String
    var DateTime : String
    var SaleSoftware : SaleSoftwareStr
    var SaleTerminalData : SaleTerminalDataStr
}

struct SaleSoftwareStr :  Decodable {
    var ProviderIdentification : String
    var ApplicationName : String
    var SoftwareVersion : String
    var CertificationCode : String
}

struct SaleProfileStr : Decodable {
    var GenericProfile : String
    var ServiceProfiles : String
}

struct SaleTerminalDataStr :  Decodable {

    var TerminalEnvironment : String
    var SaleCapabilities : String
    var SaleProfile : SaleProfileStr
}

// ------------------- PaymentRequest Body -------------------
struct PaymentRequest :  Decodable {
    var SaleData : SaleData
    var PaymentTransaction : PaymentTransaction
    var PaymentData : PaymentData
}

struct SaleData : Decodable {
    var SaleTransactionID : SaleTransactionID
}

struct PaymentTransaction : Decodable {

    var AmountsReq : AmountsReq
    var TransactionConditions: TransactionConditions

}

struct SaleTransactionID: Decodable {
    var TransactionID : String
    var TimeStamp : String
}

struct AmountsReq: Decodable {
    var Currency : String
    var RequestedAmount : String
}

struct TransactionConditions: Decodable {
    var LoyaltyHandling : String
}

struct PaymentData: Decodable {
    var PaymentType : String
}

// ------------------- MessageHeader -------------------

struct MessageHeader: Decodable {

    var MessageClass : String
    var MessageCategory : String
    var MessageType : String
    var ServiceID : String
    var SaleID : String
    var POIID : String
}

// ------------------- LoginOrPaymentRequest -------------------

enum LoginOrPaymentRequest: Decodable {


    case login(LoginRequest)
    case payment(PaymentRequest)
    case unknown

    private enum CodingKeys: String, CodingKey {

        case LoginRequest
        case PaymentRequest
    }
    // basic
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let loginRequest = try container.decodeIfPresent(LoginRequest.self, forKey: .LoginRequest) {
            self = .login(loginRequest)
        } else if let paymentRequest = try container.decodeIfPresent(PaymentRequest.self, forKey: .PaymentRequest) {
            self = .payment(paymentRequest)
        } else {
            self = .unknown
        }
    }
    // with messageCategory
    init(from decoder: Decoder, messageCategory: String) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let loginRequest = try container.decodeIfPresent(LoginRequest.self, forKey: .LoginRequest), messageCategory == "Login" {
            self = .login(loginRequest)
        } else if let paymentRequest = try container.decodeIfPresent(PaymentRequest.self, forKey: .PaymentRequest), messageCategory == "Payment" {
            self = .payment(paymentRequest)
        } else {
            self = .unknown
        }
    }
}

struct SaleToPOIRequest: Decodable {

    let messageHeader: MessageHeader?
    let loginOrPaymentRequest: LoginOrPaymentRequest

    private enum CodingKeys: String, CodingKey {

        case MessageHeader
    }

    private init(messageHeader: MessageHeader?,
                 loginOrPaymentRequest: LoginOrPaymentRequest) {
        self.messageHeader = messageHeader
        self.loginOrPaymentRequest = loginOrPaymentRequest
    }
    /* basic:
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.init(messageHeader: try container.decode(MessageHeader.self, forKey: .MessageHeader),
                  loginOrPaymentRequest: try LoginOrPaymentRequest(from: decoder))
    }
    */
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let messageHeader = try container.decode(MessageHeader.self, forKey: .MessageHeader)
        let loginOrPaymentRequest = try LoginOrPaymentRequest(from: decoder,
                                                              messageCategory: messageHeader.MessageCategory)
        self.init(messageHeader: messageHeader,
                  loginOrPaymentRequest: loginOrPaymentRequest)
    }
}

struct DataResponse: Decodable {

    let saleToPOIRequest: SaleToPOIRequest?

    private enum CodingKeys: String, CodingKey {
        case SaleToPOIRequest
    }

    private init(saleToPOIRequest: SaleToPOIRequest?) {
        self.saleToPOIRequest = saleToPOIRequest
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.init(saleToPOIRequest: try container.decode(SaleToPOIRequest.self, forKey: .SaleToPOIRequest))
    }
}


// Test it:
var dataResponse = try JSONDecoder().decode(DataResponse.self, from: json1)
print(dataResponse.saleToPOIRequest?.messageHeader?.MessageCategory as Any)
if let loginOrPaymentRequest = dataResponse.saleToPOIRequest?.loginOrPaymentRequest {
    if case .login(let loginRequest) = loginOrPaymentRequest {
        print("loginRequest.OperatorLanguage = \(loginRequest.OperatorLanguage)")
    } else if case .payment(let paymentRequest) = loginOrPaymentRequest {
        print("paymentRequest.PaymentData.PaymentType = \(paymentRequest.PaymentData.PaymentType)")
    }
}

dataResponse = try JSONDecoder().decode(DataResponse.self, from: json2)
print(dataResponse.saleToPOIRequest?.messageHeader?.MessageCategory as Any)
if let loginOrPaymentRequest = dataResponse.saleToPOIRequest?.loginOrPaymentRequest {
    if case .login(let loginRequest) = loginOrPaymentRequest {
        print("loginRequest.OperatorLanguage = \(loginRequest.OperatorLanguage)")
    } else if case .payment(let paymentRequest) = loginOrPaymentRequest {
        print("paymentRequest.PaymentData.PaymentType = \(paymentRequest.PaymentData.PaymentType)")
    }
}