Home > database >  Swift iOS Formatting data from JSON codable / API with CustomStringConvertible
Swift iOS Formatting data from JSON codable / API with CustomStringConvertible

Time:07-08

I have some JSON that I would like to reformat before use, preferably in an initializer (or extension ??)

[
{
 "name": "Diesel",
 "id": "1",
 "maj": "2022-07-06 18:28:29",
 "value": "2.81"
  },
{
 "name": "SP95",
 "id": "5",
 "maj": "2022-07-06 18:28:29",
 "value": "2.048"
  }
] 

I would like to ensure that the "value" data is always 3 decimal places. So in the above 2.810 instead of 2.81.

I have looked at CustomStringConvertible and in theory it looks possible, but I haven't managed to build a working version.

Mainly working from here https://www.swiftbysundell.com/articles/formatting-numbers-in-swift/

my Model looks like this :

struct Price: Codable {
    let name: String
    let id, maj: String?
    var value: String?
    var isCheapest: Bool?
        
}

I understand that I need to do some basic number formatting but I don't see how to integrate it with CustomStringConvertible or if this is the correct way to go about it. Any help appreciated.

CodePudding user response:

Use extension

extension Metric: CustomStringConvertible {
    var description: String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.maximumFractionDigits = 3

        let number = NSNumber(value: value)
        let formattedValue = formatter.string(from: number)!
        return "\(name): \(formattedValue)"
    }
}

CodePudding user response:

I think you can use it like below

Extension to the string

extension String {
    var doubleValue: Double? {
        get{
            Double(self)
        }
    }

    var formatedValue: String? {
        get{
            // YOU HAVE TO MAKE SURE THAT THE STRING IS CONVERTABLE TO `DOUBLE`
            String(format: "%.3f", self.doubleValue ?? 0.000)
        }
    }
}

Usage:

let price = Price(name: "Diesel", id: "1", maj: "2022-07-06 18:28:29", value: "2.81", isCheapest: false)
if let value = price.value?.formatedValue{
    print(value)
}
//Console Output - 2.810

CodePudding user response:

I would replace the String value with a custom type, i.e Wrapped in the example below and implement Codable for it, which should work transparently for the underlying string value.

Also note rounding mode on number formatter, if you want to simply clip the fraction, then .floor should do it.

import Foundation

private let formatter: NumberFormatter = {
    let formatter = NumberFormatter()
    formatter.decimalSeparator = "."
    formatter.maximumFractionDigits = 3
    formatter.roundingMode = .floor
    return formatter
}()

struct Value: Codable {
    var wrapped: Wrapped
}

struct Wrapped: Codable {
    let source: NSNumber

    var doubleValue: Double {
        return source.doubleValue
    }

    init(_ doubleValue: Double) {
        source = NSNumber(value: doubleValue)
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let stringValue = try container.decode(String.self)

        guard let numberObject = formatter.number(from: stringValue) else {
            throw DecodingError.dataCorrupted(
                DecodingError.Context(
                    codingPath: container.codingPath,
                    debugDescription: "Cannot parse NSNumber from String")
            )
        }

        source = numberObject
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()

        guard let stringValue = formatter.string(from: source) else {
            throw EncodingError.invalidValue(
                source,
                EncodingError.Context(
                    codingPath: container.codingPath,
                    debugDescription: "Cannot convert NSNumber to String"
                )
            )
        }

        try container.encode(stringValue)
    }

}

let value = Value(wrapped: Wrapped(11.234567899))
let data = try! JSONEncoder().encode(value)
let stringData = String(data: data, encoding: .utf8)!

// Prints: {"wrapped": "11.234"}
print("\(stringData)")


// Prints 11.234
let reverse = try JSONDecoder().decode(Value.self, from: data)
print("\(reverse.wrapped.source)")

  • Related