0

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

1
  • 3
    Please conform to the naming convention that variables start with a lowercase letter. This avoids ambiguity / confusion like var PaymentData : PaymentData Commented Dec 3, 2018 at 11:49

2 Answers 2

1

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)
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot for your help.
0

So finally it looks like this:

import Cocoa
//
// https://stackoverflow.com/questions/53592884/json-decoding-into-variant-model-swift4/53617310#53617310
//

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)")
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.