Search code examples
iosjsonswiftcodabledecodable

How to decode any json value to string with Decoadable object in Swift?


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.


Solution

  • 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
        }
    }