0

According my question, I want to decode every fields of my json to string value.

My json look like this

{ name: "admin_tester",
  price: 99.89977202, 
  no: 981,
  id: "nfs-998281998",
  amount: 98181819911019.828289291329 }

And I want to create my struct like this

struct StockNFS: Decodable {
     let name: String?
     let price: String?
     let no: String?
     let id: String?
     let amount: String?
}

But If I declare my struct like this, When I use json decode I will get error mismatch type

The reason why I want to mapping every value to string, It is because If I use a double or decimal for price and amount, after encode sometime value will incorrect. example 0.125, I wil got 0.124999999.

I just want to recieve any data in string type for just showing on ui ( not edit or manipulate value )

I will appreciate any help. Thank you so much.

Tar journey
  • 379
  • 4
  • 13
  • You need a custom `init(from:)` where you decode your double values and perform the correct rounding. Some notes, why not use `Decimal` instead of `String` for the price and amount and why do you need to convert the `Int` value to a string since there is no rounding issue for Int. And why is everything optional in your struct? – Joakim Danielson Nov 17 '21 at 18:14
  • @Joakim thank you so much for your reply, I don't use decimal because sometime I got wrong value mapping from json like 0.125 I got 0.12499999 after decode. I set optional because I want to made nullable value (maybe sometime api didn't return that keys after it decode it can be nil.) – Tar journey Nov 18 '21 at 06:19
  • Can you give me some example about custom init() ? @Joakim – Tar journey Nov 18 '21 at 06:20

1 Answers1

0

To avoid the floating point issues we can either use a type String or Decimal for the keys price and amount. In either case we can not decode directly into either type but we first need to use the given type which is Double so we need a custom init for this.

First case is to use String (I see no reason to use optional fields as default, change this if any of the fields actually can be nil)

struct StockNFS: Codable {
    let name: String
    let price: String
    let no: Int
    let id: String
    let amount: String

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        let priceValue = try container.decode(Double.self, forKey: .price)
        price = "\(priceValue.roundToDecimal(8))"
        //... rest of the values
    }
}

The rounding is used with a method inspired from this excellent answer

 extension Double {
    func roundToDecimal(_ fractionDigits: Int) -> Double {
        let multiplier = pow(10, Double(fractionDigits))
        return (self * multiplier).rounded() / multiplier
    }
}

To do the same but with the numeric type Decimal we do

struct StockNFS2: Codable {
    let name: String
    let price: Decimal
    let no: Int
    let id: String
    let amount: Decimal
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        let priceValue = try container.decode(Double.self, forKey: .price)
        price = Decimal(priceValue).round(.plain, precision: 8)
        //... rest of the values
    }
}

Again the rounding method was inspired from the same answer

extension Decimal {
    func round(_ mode: Decimal.RoundingMode, precision: Int = 2) -> Decimal {
        var result = Decimal()
        var value = self
        NSDecimalRound(&result, &value, precision, mode)
        return result
    }
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52