I have two JSON requests. There is a header and a different body depending of MessageCategory in the header. How do I decode this in ONE playground.
The first playground is:
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)
}
The other playground is:
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)
}
How could I decode depending on MessageCategory in the MessageHeader?
Is there something link a UNION?
Kind Regards
As far as I can see in your case, SaleToPOIRequest
contains a property of type PaymentRequestStr
or LoginRequestStr
. I would recommend you to declare enum
with associated values to store an instance of PaymentRequestStr
or LoginRequestStr
. Check this code snippet:
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
}
}
}
Also change the implementation of SaleToPOIRequest
, add a property of type 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))
}
}
This structure contains the information about an instance of 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))
}
}
How to test it:
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)
}
}
I remember that you mentioned that you need to check the value of MessageCategory
. In such case add this function to 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
}
}
And modify init
of SaleToPOIRequest
:
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)
}