Appracatappra/SimpleSerializer
A simple utility for Serializing a Swift object in the smallest space possible by converting it to a Divider separated String.
Support
If you find SimpleSerialize useful and would like to help support its continued development and maintenance, please consider making a small donation, especially if you are using it in a commercial product:
<a href="https://www.buymeacoffee.com/KevinAtAppra" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
It's through the support of contributors like yourself, I can continue to build, release and maintain high-quality, well documented Swift Packages like SimpleSerialize for free.
Installation
Swift Package Manager (Xcode 11 and above)
- In Xcode, select the File > Add Package Dependency… menu item.
- Paste
https://github.com/Appracatappra/SimpleSerializer.gitin the dialog box. - Follow the Xcode's instruction to complete the installation.
Why not CocoaPods, or Carthage, or etc?
Supporting multiple dependency managers makes maintaining a library exponentially more complicated and time consuming.
Since, the Swift Package Manager is integrated with Xcode 11 (and greater), it's the easiest choice to support going further.
Overview
`SimpleSerializer` provides an easy way to simply **Serialize** and **Deserialize** a Swift object in the smallest space possible by converting it to a **Divider** separated `String`. Additionally, you have total control over the Properties that get encoded/decoded.
### Keeping Track of Your Divider Characters
You can define an `enum` to keep track the divider's that you are using for **Serialization**. For Example:
```swift
enum Divider:String, Codable {
case gameTile = ";"
case tileElement = ">"
case tileBag = "<"
case word = "/"
case player = "$"
case playerElement = "^"
case gridCell = "!"
case gridCellElement = "@"
case gridCol = "#"
case bonus = "%"
case gameState = "|"
case storedWord = "&"
}
```
### Simple Use Case
The following code shows an example of **Serializing** and **Deserializing** a simple object:
```swift
@Observable class BonusSpot {
// MARK: - Properties
/// The amount of the bonus.
var multiplier:Int = 0
/// If `true`, the bonus is active, else it is not.
var isEnabled:Bool = false
// MARK: - Computed Properties
/// Returns the `BonusSpot` as a serialized string.
var serialized:String {
let serializer = Serializer(divider: Divider.bonus)
.append(multiplier)
.append(isEnabled)
return serializer.value
}
// MARK: - Initializers
/// Creates a new instance.
/// - Parameters:
/// - multiplier: The bonus amount.
/// - isEnabled: If `true`, the bonus is active, else it is not.
init(multiplier: Int, isEnabled: Bool = true) {
self.multiplier = multiplier
self.isEnabled = isEnabled
}
/// Creates a new instance from a serialized string.
/// - Parameter value: The serialized string representing the `BonusSpot`.
init(from value:String) {
let deserializer = Deserializer(text: value, divider: Divider.bonus)
self.multiplier = deserializer.int()
self.isEnabled = deserializer.bool()
}
}
```
> Remember to keep the same order when **Serializing** and **Deserializing** a given property, as they are dependent on the specific order.
### Encoding/Decoding Enumerations
**SimpleSerializer** has built-in methods for enumerations that have a `RawValue` of either `String` or `Int`. Take a look at the following examples.
For Strings:
```swift
enum TestValues: String, SimpleSerializeableEnum {
case one = "1"
case two = "2"
case three = "3"
}
...
var test:TestValues = .two
let serializer = SimpleSerializer.Serializer(divider: ",")
.append(test)
let value = serializer.value
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ",")
test = deserializer.stringEnum() // Test will equal .two
```
For Integers:
```swift
enum TestInts: Int, SimpleSerializeableEnum {
case one = 1
case two = 2
case three = 3
}
...
var test:TestInts = .two
let serializer = SimpleSerializer.Serializer(divider: ",")
.append(test)
let value = serializer.value
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ",")
test = deserializer.intEnum() // Test will equal .two
```
Optionally, you can use the following method to decode enumerations:
```swift
enum TestInts: Int, Hashable {
case one = 1
case two = 2
case three = 3
}
...
var test:TestInts = .two
let serializer = SimpleSerializer.Serializer(divider: ",")
.append(test)
let value = serializer.value
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ",")
// Get the next value and default to first case if
// Unable to decode.
test = deserializer.value() ?? .one
```
### Encoding/Decoding Conforming Classes
**SimpleSerializer** includes methods for working with Classes that conform to the `SimpleSerializeable` protocol. Take a look at the following example:
```swift
class ChildClass: SimpleSerializeable {
var one: String = ""
var two: String = ""
var serialized: String {
let serializer = SimpleSerializer.Serializer(divider: ":")
.append(one)
.append(two)
return serializer.value
}
init(one:String, two:String) {
self.one = one
self.two = two
}
required init(from value: String) {
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ":")
self.one = deserializer.string()
self.two = deserializer.string()
}
}
class ParentClass: SimpleSerializeable {
var firstChild: ChildClass = ChildClass(one: "", two: "")
var secondChild: ChildClass = ChildClass(one: "", two: "")
var serialized: String {
let serializer = SimpleSerializer.Serializer(divider: ",")
.append(firstChild)
.append(secondChild)
return serializer.value
}
init(firstChild:ChildClass, secondChild:ChildClass) {
self.firstChild = firstChild
self.secondChild = secondChild
}
required init(from value: String) {
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ",")
self.firstChild = deserializer.child()
self.secondChild = deserializer.child()
}
}
...
let parent:ParentClass = ParentClass(firstChild: ChildClass(one: "1", two: "2"), secondChild: ChildClass(one: "3", two: "4"))
let value = parent.serialized
let newParent = ParentClass(from: value)
```
### Encoding/Decoding Generics
**SimpleSerializer** includes methods for working with generics:
```swift
let serializer = SimpleSerializer.Serializer(divider: ",")
.append(1)
.append(2)
let deserializer = SimpleSerializer.Deserializer(text: serializer.value, divider: ",")
let x:Int = deserializer.value() ?? 0
let y:Int = deserializer.value() ?? 0
```
### Encoding/Decoding Arrays
**SimpleSerializer** includes methods for working with arrays:
```swift
let names:[String] = ["one", "two", "three"]
let numbers:[Int] = [1, 2, 3]
let serializer = SimpleSerializer.Serializer(divider: ",")
.append(array: names, divider: ":")
.append(array: numbers, divider: ";")
let value = serializer.value
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ",")
let words:[String] = deserializer.array(divider: ":")
```
This also works for `nil` arrays:
```swift
let names:[String?] = ["one", nil, "two"]
let serializer = SimpleSerializer.Serializer(divider: ",")
.append(array: names, divider: ":")
let value = serializer.value
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ",")
let words:[String?] = deserializer.nilArray(divider: ":")
```
### Encoding/Decoding Dictionaries
**SimpleSerializer** includes methods for working with Dictionaries:
```swift
var state:[String:String] = [:]
state["1"] = "One"
state["2"] = "Two"
state["3"] = "Three"
let serializer = SimpleSerializer.Serializer(divider: ":")
.append(dictionary: state)
let value = serializer.value
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ":")
let newState:[String:String] = deserializer.dictionary()
```
### Encoding/Decoding Base64 Encoded & Obfuscated Strings
**SimpleSerializer** includes methods for working with Base64 Encoded Strings:
```swift
let script = """
import StandardLib;
main {
var s:string = 'Hello world';
call @print($s);
}
"""
let serializer = SimpleSerializer.Serializer(divider: ",")
.append(script, isBase64Encoded: true)
let value = serializer.value
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ",")
let newScript = deserializer.string(isBase64Encoded: true)
```
Additionally, you can obfuscate a `String` value:
```swift
let secret = "A hidden message"
let serializer = SimpleSerializer.Serializer(divider: ",")
.append(secret, isObfuscated: true)
let value = serializer.value
let deserializer = SimpleSerializer.Deserializer(text: value, divider: ",")
let hidden = deserializer.string(isObfuscated: true)
```
> **WARNING!** This is NOT a cryptographically secure process! It's only meant to hide specific values against casual "prying-eyes".
### Complex Use Case
The following code shows an example of **Serializing** and **Deserializing** a complex object:
```swift
@Observable class GameGridCell {
// MARK: - Properties
/// The current tile that has been played at this location.
var playedTile:GameTile? = nil
/// A tile that the current player is starting to build a new word with.
var inPlayTile:GameTile? = nil
/// An available bonus at this grid location.
var bonus:BonusSpot? = nil
// MARK: - Computed Properties
/// Returns the gird cell as a serialized string.
var serialized:String {
let serializer = Serializer(divider: Divider.gridCell)
.append(encodeTile(playedTile))
.append(encodeTile(inPlayTile))
.append(encodeBonus(bonus))
return serializer.value
}
/// Returns the tile currently on top at this location or `nil` if no tile is available.
var tile:GameTile? {
if let tile = inPlayTile {
return tile
} else {
return playedTile
}
}
// MARK: - Initializers
/// Creates a new empty instance.
init() {
}
/// Creates a new instance from a serialized string.
/// - Parameter value: The serialized string holding a grid cell.
init(from value:String) {
let deserializer = Deserializer(text: value, divider: Divider.gridCell)
self.playedTile = decodeTile(from: deserializer.string())
self.inPlayTile = decodeTile(from: deserializer.string())
self.bonus = decodeBonus(from: deserializer.string())
}
// MARK: - Functions
/// Encodes a `GameTile`.
/// - Parameter tile: The tile to encode.
/// - Returns: Returns the tile as a serialized string or `(e)` if the `GameTile` is `nil`
private func encodeTile(_ tile:GameTile?) -> String {
if let tile {
return tile.serialized
} else {
return "(e)"
}
}
/// Decodes a `GameTile` from the given serialized string.
/// - Parameter value: The serialized string holding the `GameTile`.
/// - Returns: Returns the `GameTile` or `nil` if the value was `(e)`.
private func decodeTile(from value:String) -> GameTile? {
if value == "(e)" {
return nil
} else {
return GameTile(from: value)
}
}
/// Encodes a `BonusSpot`.
/// - Parameter bonus: The `BonusSpot` to encode.
/// - Returns: Returns the encoded `BonusSpot` or `(e)` if the spot was `nil`.
private func encodeBonus(_ bonus:BonusSpot?) -> String {
if let bonus {
return bonus.serialized
} else {
return "(e)"
}
}
/// Decodes a `BonusSpot` from the given serialized string.
/// - Parameter value: The serialized string holding the `BonusSpot`.
/// - Returns: Returns the `BonusSpot` or `nil` if the value was `(e)`.
private func decodeBonus(from value:String) -> BonusSpot? {
if value == "(e)" {
return nil
} else {
return BonusSpot(from: value)
}
}
}
```
> Any `nil` properties will be encoded to the string property `"(e)"`.
# Documentation
The **Package** includes full **DocC Documentation** for all features.Package Metadata
Repository: Appracatappra/SimpleSerializer
Stars: 3
Forks: 0
Open issues: 0
Default branch: main
Primary language: swift
License: MIT
README: README.md