This package is generic code for decode and encode JSON data, as part of reading and writing data to local storage. Data are tasks, logrecords and user configuration.
A Swift package for encoding and decoding JSON data with support for strings, files, and remote URLs.
DecodeEncodeGeneric provides a type-safe, generic approach to working with JSON in Swift. It handles common encoding/decoding scenarios while providing detailed error information and flexible configuration options.
- Generic decoding from strings, files, URLs, and raw Data
- Generic encoding to strings, files, and Data
- Configurable JSON encoder/decoder with custom strategies
- Comprehensive error handling with descriptive error messages
- Support for both single objects and arrays
- Async/await support for remote URL fetching
- Result-based API alternatives
- Pretty-printing convenience methods
// Basic initialization
letdecoder=DecodeGeneric()
// Custom configuration
letdecoder=DecodeGeneric(
urlSession:.shared,
jsonDecoder:JSONDecoder(),
dateDecodingStrategy:.iso8601,
keyDecodingStrategy:.convertFromSnakeCase
)letjsonString="""{"name": "John","age": 30}"""structPerson:Decodable{letname:Stringletage:Int}do{letperson=try decoder.decode(Person.self, fromString: jsonString)print(person.name) // "John"
}catch{print("Error: \(error.localizedDescription)")}letjsonArray="""[ {"name": "John", "age": 30}, {"name": "Jane", "age": 25}]"""do{letpeople=try decoder.decodeArray(Person.self, fromString: jsonArray)print(people.count) // 2
}catch{print("Error: \(error)")}do{letperson=try decoder.decode(Person.self, fromFile:"/path/to/file.json")
// or for arrays
letpeople=try decoder.decodeArray(Person.self, fromFile:"/path/to/array.json")}catch{print("Error: \(error)")}Requires macOS 12.0+ or iOS 15.0+
do{letperson=tryawait decoder.decode(Person.self, fromURL:"https://api.example.com/person")
// or for arrays
letpeople=tryawait decoder.decodeArray(Person.self,
fromURL:"https://api.example.com/people")}catch{print("Error: \(error)")}letjsonData:Data= // ... your data
do{letperson=try decoder.decode(Person.self, from: jsonData)
// or for arrays
letpeople=try decoder.decodeArray(Person.self, from: jsonData)} catch {print("Error: \(error)")}// Basic initialization
letencoder=EncodeGeneric()
// Custom configuration
letencoder=EncodeGeneric(
jsonEncoder:JSONEncoder(),
outputFormatting:[.prettyPrinted,.sortedKeys],
dateEncodingStrategy:.iso8601,
keyEncodingStrategy:.convertToSnakeCase
)
// Pretty-printed convenience initializer
letprettyEncoder=EncodeGeneric.prettyPrinted()structPerson:Encodable{letname:Stringletage:Int}letperson=Person(name:"John", age:30)do{letdata=try encoder.encode(person)
// or for arrays
letpeople=[person]letarrayData=try encoder.encodeArray(people)}catch{print("Error: \(error)")}do{letjsonString=try encoder.encodeToString(person)print(jsonString) // {"name":"John","age":30}
// Pretty-printed string
letprettyString=try encoder.encodeToPrettyString(person)
// Array to string
letarrayString=try encoder.encodeArrayToString([person])}catch{print("Error: \(error)")}do{try encoder.encode(person, toFile:"/path/to/output.json")
// With atomic write option (default is true)
try encoder.encode(person, toFile:"/path/to/output.json", atomically:true)
// Array to file
try encoder.encodeArray([person], toFile:"/path/to/array.json")}catch{print("Error: \(error)")}For situations where you prefer Result types over throwing functions:
letresult= encoder.encodeResult(person)switch result {case.success(let data):print("Encoded successfully")case.failure(let error):print("Error: \(error)")}letstringResult= encoder.encodeToStringResult(person)switch stringResult {case.success(let jsonString):print(jsonString)case.failure(let error):print("Error: \(error)")}The package provides comprehensive error types with descriptive messages:
.invalidStringEncoding- Failed to encode string as UTF-8 data.invalidURL- Invalid URL string.invalidFilePath- Invalid file path.decodingFailed(Error)- JSON decoding failed with underlying error.fileReadFailed(Error)- Failed to read file with underlying error
.encodingFailed(Error)- JSON encoding failed with underlying error.fileWriteFailed(Error)- Failed to write file with underlying error.invalidFilePath- Invalid file path.stringConversionFailed- Failed to convert data to UTF-8 string
letdecoder=DecodeGeneric(
dateDecodingStrategy:.iso8601
)letencoder=EncodeGeneric(
dateEncodingStrategy:.iso8601
)letdecoder=DecodeGeneric(
keyDecodingStrategy:.convertFromSnakeCase
)letencoder=EncodeGeneric(
keyEncodingStrategy:.convertToSnakeCase
)letconfig=URLSessionConfiguration.default
config.timeoutIntervalForRequest =30letcustomSession=URLSession(configuration: config)letdecoder=DecodeGeneric(urlSession: customSession)- Decoding flows through
decodeData/decodeDataArrayhelpers so all entry points share the same error mapping toDecodeError. - Encoding uses
some Encodablefor tighter type safety while still accepting any codable type; arrays use the same helper for consistent failures. prettyPrinted()builds an encoder with sorted keys and pretty print for deterministic output without altering the primary encoder configuration.- File helpers guard empty paths up-front and return
.invalidFilePath; IO and string conversion errors surface as typedEncodeError/DecodeErrorfor clearer logging and tests.
- Swift 5.7+
- macOS 10.15+ / iOS 13.0+ (for basic functionality)
- macOS 12.0+ / iOS 15.0+ (for async URL decoding)
Both DecodeGeneric and EncodeGeneric are marked as final classes. The underlying JSONDecoder and JSONEncoder are thread-safe for read operations, but you should create separate instances for concurrent encoding/decoding operations.
MIT
Thomas Evensen