2

I want to modify the value of roomTotal key while mapping. Please refer following code:

struct TransactionInfoModel: Codable {
   var currencyCode: String
   var total: Double
   var roomCurrencyCode: String
   var exchangeRate: Double

   var roomTotal: Double {
      get {
        let y = (self.roomTotal*100).rounded()/100
        return y
      }
   }
}

Initially, roomTotal key having the value from the server like "427.3000000002" but I want while mapping it stores value "427.3". Above code is not working.

Looking for some suggestions to resolve the issue.

Shiv Jaiswal
  • 539
  • 1
  • 6
  • 22
  • 2
    Use `Decimal`, not `Double` – Alexander Feb 05 '18 at 03:32
  • @Alexander I tried with Decimal but still facing same issue. – Shiv Jaiswal Feb 05 '18 at 03:35
  • Do you really need the value to be changed? Or do you just want to make sure that when you display it in the UI, that you only see two decimal places? If you need to actually round and accurately represent this with precisely two decimal places (e.g. you're doing math based upon many of these values), then you should use `Decimal`. If you only need to make sure that you only display two decimal places, then you might make sure to use `NumberFormatter` when you show the results and ignore any negligible imprecisions that `Double` introduces and keep your `Codable` logic simple. – Rob Feb 05 '18 at 05:58
  • @Rob Yes, I need to change the value and earlier I tried with `Decimal` but its not working. – Shiv Jaiswal Feb 05 '18 at 06:22

1 Answers1

1

See this answer for example of how to round Double values and return a Decimal value that will preserve the rounding, without any of the artifacts that result when trying to represent decimal values in binary floating point representations. Then, you can get the rounded Decimal value like so:

let roomTotal = 427.3000000002
let value = roomTotal.roundedDecimal(to: 2)

And if you want to display it with a specified number of decimal places (as well as localize the string for the user's current locale), you can use a NumberFormatter:

let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2

if let string = formatter.string(for: value) {
    print(string)
}

Now, if you want the JSONDecoder to do this conversion for you, you can, but I might be inclined to have the struct accurately represent what was returned, avoid any convoluted init(from:) implementation, but perhaps include a computed property to return the value in the format you want, e.g.:

struct TransactionInfoModel: Codable {
   var currencyCode: String
   var total: Double
   var roomCurrencyCode: String
   var exchangeRate: Double
   var roomTotal: Double

   var roundedRoomTotal: Decimal {
       return roomTotal.roundedDecimal(to: 2)
   }
}

If you need to round the roomTotal when you decode it, you can write an init(from:) to do that:

struct TransactionInfoModel: Codable {
    var currencyCode: String
    var total: Double
    var roomCurrencyCode: String
    var exchangeRate: Double
    var roomTotal: Decimal

    init(from decoder: Decoder) throws {
        let values       = try decoder.container(keyedBy: CodingKeys.self)
        currencyCode     = try values.decode(String.self, forKey: .currencyCode)
        total            = try values.decode(Double.self, forKey: .total)
        roomCurrencyCode = try values.decode(String.self, forKey: .roomCurrencyCode)
        exchangeRate     = try values.decode(Double.self, forKey: .exchangeRate)
        roomTotal        = try values.decode(Double.self, forKey: .roomTotal).roundedDecimal(to: 2)
    }
}

Alternatively, you could omit the init(from:), but manually round it after parsing:

struct TransactionInfoModel: Codable {
    var currencyCode: String
    var total: Double
    var roomCurrencyCode: String
    var exchangeRate: Double
    var roomTotal: Decimal
}

And:

var object = try! JSONDecoder().decode(TransactionInfoModel.self, from: data)
var rounded = Decimal()
NSDecimalRound(&rounded, &object.roomTotal, 2, .plain)
object.roomTotal = rounded
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks, @Rob according to your code `roomTotal` and `roundedRoomtotal` will hold values `427.3000000002` and `427.30` respectively. Which is fine but I am facing one more problem that is I have to send all data to the server as it comes from the server. As per this need, `roomTotal` key should have the value `427.30`. For this when I tried to assign `roundedRoomtotal` key's value to `roomTotal` key I had to convert it into the double which again creates the same problem. – Shiv Jaiswal Feb 05 '18 at 07:51
  • See a few options in my expanded answer – Rob Feb 05 '18 at 09:31