A tiny, zero-dependency Swift package of the helpers you end up rewriting in every project: file decoding, collection math, and a lightweight MVVM binding primitive.
- Platforms: iOS 18+ / macOS 10.15+
- Swift tools: 6.0+
Swift Package Manager. Add it in Xcode via File → Add Packages… with https://github.com/mchirino89/MauriUtils, or in your Package.swift:
dependencies: [
.package(url: "https://github.com/mchirino89/MauriUtils", from: "2.0.0")
]FileReader reads bundled or on-disk files and decodes JSON/plist straight into your Decodable models.
let reader = FileReader()
// Raw data from the app bundle (defaults to .json)
let data = reader.read(from: "config")
// Decode a bundled JSON file in one call
let user: User = try reader.decodeJSON(from: "user")
// Decode a bundled plist
let settings: Settings = try reader.decodePlist(from: "Settings")
// Read a UTF-8 text file at an absolute path
let raw = reader.readText(atPath: "/tmp/interview.csv")Need finer control? JSONDecodable.map and PlistDecodable.map decode raw Data with custom key/date strategies:
let model: Model = try JSONDecodable.map(input: data,
with: .convertFromSnakeCase,
dateDecoding: .iso8601)Both throw DecodeException (.notFound / .unparseable) so failures are explicit.
DelimitedText splits plain-text datasets into lines or trimmed field rows.
DelimitedText.lines(raw) // non-empty lines
DelimitedText.rows(raw) // comma-separated fields
DelimitedText.rows(raw, separator: "\t")Naive splitter — no quoted-field/embedded-delimiter handling. Fine for clean datasets; reach for a real RFC-4180 parser if your cells contain quoted commas.
[3.0, 4.0, 5.0].average // 4.0
[1, 2, 2, 3].numberOfOccurrences(of: 2) // 2
[1, 2, 3, 4].count(where: { $0 > 2 }) // 2
[1, 2, 3].sum // 6
[Int].generateRandomElements(arraySize: 5)
var list = [1, 2, 3]
list.rearrange(from: 0, to: 2) // [2, 3, 1]
list.mergeElements(newElements: [3, 4]) // [2, 3, 1, 4] (dedupes)
list.removing(2) // returns a copy without 2
list.isSingleElement // falseDynamic<T> is a minimal observable box; DataSource<T> wraps a dynamic array for table/collection views.
let name = Dynamic("")
name.update { print("changed to \($0)") }
name.value = "Mauricio" // fires the listener
let source = DataSource<Row>()
source.data.update { rows in /* reload UI */ }executeMainThreadUpdate {
// runs now if already on main, otherwise dispatched to it
}Pull requests welcome — I'll happily review them. Thanks!