Skip to content

rsyncOSX/DecodeEncodeGeneric

Repository files navigation

Hi there 👋

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.

DecodeEncodeGeneric Documentation

A Swift package for encoding and decoding JSON data with support for strings, files, and remote URLs.

Overview

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.

Features

  • 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

DecodeGeneric

Initialization

// Basic initialization
let decoder = DecodeGeneric()

// Custom configuration
let decoder = DecodeGeneric(
    urlSession: .shared,
    jsonDecoder: JSONDecoder(),
    dateDecodingStrategy: .iso8601,
    keyDecodingStrategy: .convertFromSnakeCase
)

Decoding from Strings

let jsonString = """
{
    "name": "John",
    "age": 30
}
"""

struct Person: Decodable {
    let name: String
    let age: Int
}

do {
    let person = try decoder.decode(Person.self, fromString: jsonString)
    print(person.name) // "John"
} catch {
    print("Error: \(error.localizedDescription)")
}

Decoding Arrays from Strings

let jsonArray = """
[
    {"name": "John", "age": 30},
    {"name": "Jane", "age": 25}
]
"""

do {
    let people = try decoder.decodeArray(Person.self, fromString: jsonArray)
    print(people.count) // 2
} catch {
    print("Error: \(error)")
}

Decoding from Files

do {
    let person = try decoder.decode(Person.self, fromFile: "/path/to/file.json")
    // or for arrays
    let people = try decoder.decodeArray(Person.self, fromFile: "/path/to/array.json")
} catch {
    print("Error: \(error)")
}

Decoding from URLs (Async)

Requires macOS 12.0+ or iOS 15.0+

do {
    let person = try await decoder.decode(
        Person.self, 
        fromURL: "https://api.example.com/person"
    )
    
    // or for arrays
    let people = try await decoder.decodeArray(
        Person.self,
        fromURL: "https://api.example.com/people"
    )
} catch {
    print("Error: \(error)")
}

Decoding from Data

let jsonData: Data = // ... your data
do {
    let person = try decoder.decode(Person.self, from: jsonData)
    // or for arrays
    let people = try decoder.decodeArray(Person.self, from: jsonData)
} catch {
    print("Error: \(error)")
}

EncodeGeneric

Initialization

// Basic initialization
let encoder = EncodeGeneric()

// Custom configuration
let encoder = EncodeGeneric(
    jsonEncoder: JSONEncoder(),
    outputFormatting: [.prettyPrinted, .sortedKeys],
    dateEncodingStrategy: .iso8601,
    keyEncodingStrategy: .convertToSnakeCase
)

// Pretty-printed convenience initializer
let prettyEncoder = EncodeGeneric.prettyPrinted()

Encoding to Data

struct Person: Encodable {
    let name: String
    let age: Int
}

let person = Person(name: "John", age: 30)

do {
    let data = try encoder.encode(person)
    // or for arrays
    let people = [person]
    let arrayData = try encoder.encodeArray(people)
} catch {
    print("Error: \(error)")
}

Encoding to String

do {
    let jsonString = try encoder.encodeToString(person)
    print(jsonString) // {"name":"John","age":30}
    
    // Pretty-printed string
    let prettyString = try encoder.encodeToPrettyString(person)
    
    // Array to string
    let arrayString = try encoder.encodeArrayToString([person])
} catch {
    print("Error: \(error)")
}

Encoding to File

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

Result-Based API

For situations where you prefer Result types over throwing functions:

let result = encoder.encodeResult(person)
switch result {
case .success(let data):
    print("Encoded successfully")
case .failure(let error):
    print("Error: \(error)")
}

let stringResult = encoder.encodeToStringResult(person)
switch stringResult {
case .success(let jsonString):
    print(jsonString)
case .failure(let error):
    print("Error: \(error)")
}

Error Handling

DecodeError

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

EncodeError

  • .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

Advanced Usage

Custom Date Strategies

let decoder = DecodeGeneric(
    dateDecodingStrategy: .iso8601
)

let encoder = EncodeGeneric(
    dateEncodingStrategy: .iso8601
)

Custom Key Strategies

let decoder = DecodeGeneric(
    keyDecodingStrategy: .convertFromSnakeCase
)

let encoder = EncodeGeneric(
    keyEncodingStrategy: .convertToSnakeCase
)

Custom URLSession

let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
let customSession = URLSession(configuration: config)

let decoder = DecodeGeneric(urlSession: customSession)

Internals (Maintainers)

  • Decoding flows through decodeData/decodeDataArray helpers so all entry points share the same error mapping to DecodeError.
  • Encoding uses some Encodable for 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 typed EncodeError/DecodeError for clearer logging and tests.

Requirements

  • Swift 5.7+
  • macOS 10.15+ / iOS 13.0+ (for basic functionality)
  • macOS 12.0+ / iOS 15.0+ (for async URL decoding)

Thread Safety

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.

License

MIT

Author

Thomas Evensen

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages