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