SkillAgentSearch skills...

Money

A precise, type-safe representation of a monetary amount in a given currency

Install / Use

/learn @Flight-School/Money
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Money

[![Build Status][build status badge]][build status] [![License][license badge]][license] [![Swift Version][swift version badge]][swift version] ![Cocoapods platforms][cocoapods platforms badge] [![Cocoapods compatible][cocoapods badge]][cocoapods] [![Carthage compatible][carthage badge]][carthage]

A precise, type-safe representation of monetary amounts in a given currency.

This functionality is discussed in Chapter 3 of Flight School Guide to Swift Numbers.

Requirements

  • Swift 4.0+

Installation

Swift Package Manager

Add the Money package to your target dependencies in Package.swift:

import PackageDescription

let package = Package(
  name: "YourProject",
  dependencies: [
    .package(
        url: "https://github.com/Flight-School/Money",
        from: "1.3.0"
    ),
  ]
)

Then run the swift build command to build your project.

CocoaPods

You can install Money via CocoaPods, by adding the following line to your Podfile:

pod 'Money-FlightSchool', '~> 1.3.0'

Run the pod install command to download the library and integrate it into your Xcode project.

Note The module name for this library is "Money" --- that is, to use it, you add import Money to the top of your Swift code just as you would by any other installation method. The pod is called "Money-FlightSchool" because there's an existing pod with the name "Money".

Carthage

To use Money in your Xcode project using Carthage, specify it in Cartfile:

github "Flight-School/Money" ~> 1.3.0

Then run the carthage update command to build the framework, and drag the built Money.framework into your Xcode project.

Usage

Creating Monetary Amounts

The Money type has a required associated Currency type. These currency types are named according to their three letter [ISO 4701][iso4217] currency codes. You can initialize a monetary using a Decimal value:

let amount = Decimal(12)
let monetaryAmount = Money<USD>(amount)

Some currencies specify a minor unit. For example, USD amounts are often expressed in cents, each worth 1/100 of a dollar. You can initialize monetary amounts from a quantity of minor units. For currencies that don't have a minor unit, such as JPY, this is equivalent to the standard initializer.

let twoCents = Money<USD>(minorUnits: 2)
twoCents.amount // 0.02

let ichimonEn = Money<JPY>(minorUnits: 10_000)
ichimonEn.amount // 10000

You can also create monetary amounts using integer, floating-point, and string literals.

12 as Money<USD>
12.00 as Money<USD>
"12.00" as Money<USD>

Important: Swift floating-point literals are currently initialized using binary floating-point number type, which cannot precisely express certain values. As a workaround, monetary amounts initialized from a floating-point literal are rounded to the number of places of the minor currency unit. If you want to express a smaller fractional monetary amount, initialize from a string literal or Decimal value instead.

let preciseAmount: Money<USD> = "123.4567"
let roundedAmount: Money<USD> = 123.4567

preciseAmount.amount // 123.4567
roundedAmount.amount // 123.46

For more information, see https://bugs.swift.org/browse/SR-920.

Comparing Monetary Amounts

You can compare two monetary amounts with the same currency:

let amountInWallet: Money<USD> = 60.00
let price: Money<USD> = 19.99

amountInWallet >= price // true

Attempting to compare monetary amounts with different currencies results in a compiler error:

let dollarAmount: Money<USD> = 123.45
let euroAmount: Money<EUR> = 4567.89

dollarAmount == euroAmount // Error: Binary operator '==' cannot be applied

Adding, Subtracting, and Multiplying Monetary Amounts

Monetary amounts can be added, subtracted, and multiplied using the standard binary arithmetic operators (+, -, *):

let prices: [Money<USD>] = [2.19, 5.39, 20.99, 2.99, 1.99, 1.99, 0.99]
let subtotal = prices.reduce(0.00, +) // "$36.53"
let tax = 0.08 * subtotal // "$2.92"
let total = subtotal + tax // "$39.45"

Important: Multiplying a monetary amount by a floating-point number results in an amount rounded to the number of places of the minor currency unit. If you want to produce a smaller fractional monetary amount, multiply by a Decimal value instead.

Formatting Monetary Amounts

You can create a localized representation of a monetary amount using NumberFormatter. Set the currencyCode property of the formatter to the currency.code property of the Money value and pass the amount property to the formatter string(for:) method.

let allowance: Money<USD> = 10.00
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: "fr-FR")
formatter.currencyCode = allowance.currency.code
formatter.string(for: allowance.amount) // "10,00 $US"

Encoding and Decoding Monetary Amounts

Encoding

By default, Money values are encoded as keyed containers, with amount encoded as a number value.

let value: Money<USD> = 123.45

let encoder = JSONEncoder()
let data = try encoder.encode(value)
String(data: data, encoding: .utf8) // #"{"amount":123.45,"currencyCode":"USD"}"#

To configure encoding behavior, set either the JSONEncoder.moneyEncodingOptions property or the CodingUserInfoKey.moneyEncodingOptions key in the encoder's userInfo property.

var encoder = JSONEncoder()
encoder.moneyEncodingOptions = [.omitCurrency, .encodeAmountAsString]

let data = try encoder.encode([value])
String(data: data, encoding: .utf8) // #"["123.45"]"#

Decoding

The default decoding behavior is flexible, supporting both keyed and single value containers, with string or number values for amount.

let json = #"""
[
    { "currencyCode": "USD", "amount": "100.00" },
    50.00,
    "10"
]
"""#.data(using: .utf8)!

let decoder = JSONDecoder()
let values = try decoder.decode([Money<USD>].self, from: json)
values.first?.amount // 100.00
values.last?.currency.code // "USD"

To configure decoding behavior, set either the JSONDecoder.moneyDecodingOptions property or the CodingUserInfoKey.moneyDecodingOptions key in the decoder's userInfo property.

var decoder = JSONDecoder()
decoder.moneyDecodingOptions = [.requireExplicitCurrency]

Important: Foundation decoders currently decode number values using a binary floating-point number type, which cannot precisely express certain values. As a workaround, you can specify the requireStringAmount decoding option to require monetary amounts to be decoded precisely from a string representation.

let json = #"""
{ "currencyCode": "USD", "amount": "27.31" }
"""#.data(using: .utf8)!

var decoder = JSONDecoder()

try decoder.decode(Money<USD>.self, from: json) // DecodingError

decoder.moneyDecodingOptions = [.requireStringAmount]
let preciseAmount = try decoder.decode(Money<USD>.self, from: json)
preciseAmount.amount // 27.31

Alternatively, you can the roundFloatingPointAmount decoding option to round decoded floating-point values to the number of places of the minor currency unit.

let json = #"""
{ "currencyCode": "USD", "amount": 27.31 }
"""#.data(using: .utf8)!

var decoder = JSONDecoder()

let impreciseAmount = try decoder.decode(Money<USD>.self, from: json)
impreciseAmount.amount // 27.30999999...

decoder.moneyDecodingOptions = [.roundFloatingPointAmount]
let roundedAmount = try decoder.decode(Money<USD>.self, from: json)
roundedAmount.amount // 27.31

For more information, see https://bugs.swift.org/browse/SR-7054.

Customizing Coding Keys

By default, Money values are encoded and decoded with the string keys "amount" and "currencyCode", which correspond to their respective properties.

If you're working with data that encodes monetary amounts differently, you can set the keyDecodingStrategy property of JSONDecoder to map to different key names:

let json = #"""
 {
    "value": "3.33",
    "currency": "USD"
 }
 """#.data(using: .utf8)!

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ keys in
    switch keys.last?.stringValue {
    case "value":
        return MoneyCodingKeys.amount
    case "currency":
        return MoneyCodingKeys.currencyCode
    default:
        return keys.last!
    }
})

let amount = try decoder.decode(Money<USD>.self, from: json) // $3.33

Alternatively, you can create structures that match the shape of your data and derive computed properties that return Money types:

struct Item: Codable {
    struct Price: Codable {
        let value: String
        let currency: String
    }

    let name: String
    private let unitPrice: Price

    var unitPriceInUSD: Money<USD>? {
        guard unitPrice.currency == USD.code else { return nil }
        return Money(unitPrice.value)
    }
}

let json = #"""
 {
    "name": "Widget",
    "unitPrice": {
       "value": "3.33",
       "currency": "USD"
    }
 }
 """#.data(using: .utf8)!

let decoder = JSONDecoder()
let item = try decoder.decode(Item.self, from: json)
item.unitPriceInUSD // $3.33

Supporting Multiple Currencies

Consider a Product structure with a price property. If you only support a single currency, such as US Dollars, you would define price to be of type Money<USD>:

struct Product {
    var price: Money<USD>
}

If you want to support multiple currencies, however, you can't specify an explicit currency type in the property declaration. Instead, the Product would have to be defined as a generic type:

struct Product<Currency: CurrencyType> {
    var price: Money<Currency>
}

Unfortunately, this approach is unwieldy, as each type that interacts with Product would also need to be generic, and so on, until the entire code base is gen

Related Skills

View on GitHub
GitHub Stars910
CategoryDevelopment
Updated9d ago
Forks34

Languages

Swift

Security Score

100/100

Audited on Mar 16, 2026

No findings