Custom Decoder for struct with mulitple UInt attributes in Swift - swift

I am trying to write a generic decoder which decodes Datato a specific object.
The following object is given:
struct MyObject: Codable, Equatable {
let firsValue: UInt8
let secondValue: UInt16
let thirdValue: UInt32
}
Now i got e.g. Data from didUpdateValue from peripheral(_ peripheral: CBMPeripheral, didUpdateValueFor characteristic: CBMCharacteristic, error: Error?) and I want to transform this data to MyObject.
Is there a generic way available?
My current solution looks as follows:
internal class DataReader {
private let data: Data
private var cursor: Int = 0
init(_ data: Data) {
self.data = data
}
func readNext<T>() -> T {
// Get the number of bytes occupied by the type T
let chunkSize = MemoryLayout<T>.size
// Get the bytes that contain next value
let nextDataChunk = Data(data[cursor..<cursor+chunkSize])
// Read the actual value from the data chunk
let value = nextDataChunk.withUnsafeBytes { bufferPointer in
bufferPointer.load(fromByteOffset: 0, as: T.self)
}
// Move the cursor to the next position
cursor += chunkSize
// Return the value that we just read
return value
}
struct MyObject: Codable, Equatable {
let firsValue: UInt8
let secondValue: UInt16
let thirdValue: UInt32
init?(_ data: Data) {
let dataReader = DataReader(data)
guard data.count == 7 else {
// unexpected number of bytes
return nil
}
firsValue = dataReader.readNext()
secondValue = dataReader.readNext()
thirdValue = dataReader.readNext()
}
}
But then for each new object I need to write an own init function. Is there more easy way available?

Related

Dictionary extension for using #AppStorage

I understand I need to make Dictionary conform to RawRepresentable when using a dictionary with #AppStorage. Below is the furthest I got.
extension Dictionary: RawRepresentable where Key == String, Value == String {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8), // convert from String to Data
let result = try? JSONDecoder().decode([String:String].self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self), // data is Data type
let result = String(data: data, encoding: .utf8) // coerce NSData to String
else {
return "[:]" // empty Dictionary respresented as String
}
return result
}
}
The extension compiles. But when I declare an empty dictionary it didn't work:
#AppStorage private var data : [String:String] = [:]
The error message is Missing argument for parameter #2 in call
What's wrong with the codes?
You are missing the string key for the storage:
#AppStorage("data") private var data : [String:String] = [:]

Swift - Return type of struct parameters without default values

I'm very new in Swift so i might be missing some basics.
I have struct:
struct MyStruct {
var a: Int
var b: String
var c: Bool
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
}
and function, that should iterate through given struct properties and return their types in json:
func structProps(){
let elm = MyStruct()
let mirror = Mirror(reflecting: elm)
var exampleDict: [String: String] = [:]
for child in mirror.children {
exampleDict[child.label!] = String(describing:type(of: child.value)) as String
}
if let theJSONData = try? JSONSerialization.data(
withJSONObject: exampleDict,
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
}
}
it kinda return what i need:
JSON string = {"a":"Int","b":"String","c":"Bool"}
Because i'm having a lot of structs and i want to export json from all of them, i'm wondering if there is a way to have generic initializer. Without passing default values.
It means without
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
If I understand correctly , you can create a base protocol and add as an extension to your structures like
protocol BaseFunction {
func getElements() -> [String : String]
func getDict() -> String
}
extension BaseFunction {
func getElements() -> [String : String] {
let mirror = Mirror(reflecting: self)
let propertiesRemoveNil = mirror.children.filter({!(($0.value as AnyObject) is NSNull)})
let properties = propertiesRemoveNil.compactMap({$0.label})
var types = [String]()
_ = mirror.children.forEach({
types.append(String(describing:type(of: $0.value)))
})
return Dictionary(uniqueKeysWithValues: zip(properties, types))
}
func getDict() -> String{
if let theJSONData = try? JSONSerialization.data(
withJSONObject: getElements(),
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
return theJSONText ?? ""
}
return ""
}
}
And usage :
func structProps(){
let elm = MyStruct()
print(elm.getDict())
}
OUTPUT :
{"a":"Int","b":"String","c":"Bool"}
I was going to write a comment about using a protocol but I thought it would be easier to understand as an answer with some code.
To make the usage more generic so you don't need specific code for each type of struct you should use a protocol. Instead of having an init that might clash with already existing init in the struct I prefer a static method that returns a new object, also known as a factory method
protocol PropertyExtract {
static func createEmpty() -> PropertyExtract
}
Then we can make use of the default init for the struct or any supplied to create an object with some initial values by letting the struct conform to the protocol in an extension
extension MyStruct: PropertyExtract {
static func createEmpty() -> PropertyExtract {
MyStruct(a: 0, b: "", c: false)
}
}
And instead of hardcoding or passing a specific type of object to the encoding function we pass the type of it
func structProps(for structType: PropertyExtract.Type)
and use the protocol method to get an instance of the type
let object = structType.createEmpty()
The whole function (with some additional changes)
func structProps(for structType: PropertyExtract.Type) -> String? {
let object = structType.createEmpty()
let mirror = Mirror(reflecting: object)
let exampleDict = mirror.children.reduce(into: [String:String]()) {
guard let label = $1.label else { return }
$0[label] = String(describing:type(of: $1.value))
}
if let data = try? JSONEncoder().encode(exampleDict) {
return String(data: data, encoding: .utf8)
}
return nil
}
And this is then called with the type of the struct
let jsonString = structProps(for: MyStruct.self)
According to Creating a Swift Runtime Library, there is a way to access meta data types without initializer.
Solution for what i was asking for is possible with Runtime library.
Mirror(reflecting:) expects an instance of a type and not a type itself, ref.
One idea is to have a generic function that works with all types conforming to a protocol that provides an empty init. Something like:
protocol EmptyInitializable {
init()
}
struct StructOne {
let a: Bool
let b: String
}
struct StructTwo {
let c: Int
let d: Float
}
extension StructOne: EmptyInitializable {
init() {
a = false
b = ""
}
}
extension StructTwo: EmptyInitializable {
init() {
c = 1
d = 1.0
}
}
func print(subject: EmptyInitializable) -> String? {
let dictionary = Dictionary(uniqueKeysWithValues:
Mirror(reflecting: subject).children.map {
($0.label!, String(describing: type(of: $0.value)))
}
)
return (try? JSONSerialization.data(withJSONObject: dictionary)).flatMap {
String(data: $0, encoding: .utf8)
}
}
print(subject: StructOne()) // "{"a":"Bool","b":"String"}"
print(subject: StructTwo()) // "{"d":"Float","c":"Int"}"
You still have to implement the empty init and assign some values and I'm afraid there's no way currently to avoid that.

How Swift Vars automatically convert to My Any Type

I was doing some research on AnyXXX struct like AnyHashable, then I write a similar container named MyAnyHashable.
There's some issues
when I using the original AnyHashalbe is Swift, it works fine:
func usingAppleAnyHashable() {
var dict = Dictionary<AnyHashable, Any>()
// using literal as key always works
dict[1] = 1
dict["efg"] = "efg_value"
// using some var as key still works
let any1 = 100
let any2 = "abc"
dict[any1] = 100
dict[any2] = "abc_value"
print(dict)
}
it can automatically convert Int's var into AnyHashable, what a Magic!
let any1 = 100
let any2 = "abc"
dict[any1] = 100
dict[any2] = "abc_value"
Then, I using MyAnyHashable as the Key's Type
func usingMyAnyHashable() {
var dict = Dictionary<MyAnyHashable, Any>()
// using literal as key always works
dict[1] = 1
dict["efg"] = "efg_value"
// it show errors: Cannot convert value of type 'Int' to expected argument type 'MyAnyHashable'...
let any1 = 100
let any2 = "abc"
dict[any1] = 100
dict[any2] = "abc_value"
print(dict)
}
I must write like this to manually create a MyAnyHashable instance
dict[MyAnyHashable(any1)] = 100
What is the Magic in Apple's AnyHashable?
How to make it automatically convert non-Literal var into MyAnyHashable?
Here is MyAnyHashable implementation, I even wrote a ExpressibleByXXXXLiteral which can automatically transform literalType to MyAnyHashable.
struct MyAnyHashable : Hashable, Equatable {
private let baseHash: Int
private let base: Any
init<H: Hashable>(_ base: H) {
self.base = base
self.baseHash = base.hashValue
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.baseHash)
}
static func == (lhs: MyAnyHashable, rhs: MyAnyHashable) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
extension MyAnyHashable : ExpressibleByStringLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral {
typealias StringLiteralType = String
init(stringLiteral value: StringLiteralType) {
self.init(value)
}
typealias IntegerLiteralType = Int
init(integerLiteral value: IntegerLiteralType) {
self.init(value)
}
typealias FloatLiteralType = Float
init(floatLiteral value: FloatLiteralType) {
self.init(value)
}
}
screenshots

SwiftUI: Encode a struct to be saved in AppStorage

Currently trying to build my first app in swiftUI. The part I thought would be the easiest as become a nightmare... save a struct in AppStorage to be available upon restart of the app
I got two struct to save. The first is for player and I have implemented the RawRepresentable
struct Player: Codable, Identifiable {
let id: Int
let name: String
let gamePlayed: Int
let bestScore: Int
let nbrGameWon: Int
let nbrGameLost: Int
let totalScore: Int?
}
typealias PlayerList = [Player]
extension PlayerList: RawRepresentable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(PlayerList.self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}
Calling in my view this way:
struct AddPlayerView: View {
#State var name: String = ""
#State var isDisabled: Bool = false
#State var modified: Bool = false
#AppStorage("players") var players: PlayerList = PlayerList()
...
}
The above works, now I also want to save the current game data, I have the following struct:
struct Game: Codable, Identifiable {
var id: Int
var currentPlayerIndexes: Int
var currentRoundIndex: Int?
var dealerIndex: Int?
var maxRounds: Int?
var dealResults: [Int: Array<PlayerRoundSelection>]?
var currentLeaderIds: Array<Int>?
var isGameInProgress: Bool?
}
extension Game: RawRepresentable {
public init?(rawValue: String) {
if rawValue == "" {
// did to fix issue when calling AppStorage, but it is probably a bad idea
self = Game(id:1, currentPlayerIndexes:1)
}
else {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(Game.self, from: data)
else {
return nil
}
self = result
}
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return ""
}
return result
}
}
As soon as I try to modify the struct, it calls rawValue and the encoding fails with the following:
error: warning: couldn't get required object pointer (substituting NULL): Couldn't load 'self' because its value couldn't be evaluated
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=2, address=0x7ffee49bbff8).
Here part of the code that access the struct:
struct SelectPlayersView: View {
#AppStorage("currentGame") var currentGame: Game = Game(rawValue: "")!
....
NavigationLink(
destination: SelectModeTypeView(), tag: 2, selection: self.$selection) {
ActionButtonView(text:"Next", disabled: self.$isDisabled, buttonAction: {
var currentPlayers = Array<Int>()
self.players.forEach({ player in
if selectedPlayers.contains(player.id) {
currentPlayers.insert(player.id, at: currentPlayers.count)
}
})
// This used to be a list of indexes, but for testing only using a single index
self.currentGame.currentPlayerIndexes = 6
self.selection = 2
})
...
I found the code to encode here: https://lostmoa.com/blog/SaveCustomCodableTypesInAppStorageOrSceneStorage/
My understanding is that with the self in the encode, it generate an infinite loop hence the bad access.
I have really no knowledge how to properly encode this, any help, links would be appreciated
I had the same problem and I wanted to share my experience here.
I eventually found that apparently you cannot rely on the default Codable protocol implementation when used in combination with RawRepresentable.
So when I did my own Codable implementation, with CodingKeys and all, it worked!
I think your Codable implementation for Game would be something like:
enum CodingKeys: CodingKey {
case currentPlayerIndexes
case currentRoundIndex
// <all the other elements too>
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.currentPlayerIndexes = try container.decode(Int.self, forKey: .currentPlayerIndexes)
self.currentRoundIndex = try container.decode(Int.self, forKey: .currentRoundIndex)
// <and so on>
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(currentPlayerIndexes, forKey: .currentPlayerIndexes)
try container.encode(currentRoundIndex, forKey: .currentRoundIndex)
// <and so on>
}
I then wondered why your Player coding/decoding did work and found that the default coding and decoding of an Array (i.e. the PlayerList, which is [Player]), works fine.

Decodable not working with non empty array

I'm using this library which has created non-empty collections like arrays, dictionaries and strings. https://github.com/pointfreeco/swift-nonempty
When I decode to the non-empty array I get the following error
Swift.DecodingError.typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "poiList", intValue: nil)], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
This is my structure
struct LocationCarModel: Codable {
// MARK: - Properties
var poiList: NonEmptyArray<PointOfInterest>
// MARK: - PointOfInterest
struct PointOfInterest: Codable {
var id: Int
var coordinate: Position
var fleetType: String
let numberPlate = "HCD837EC"
let model: String = "Tesla S"
let fuel: Double = 0.9
}
}
This is the response I'm getting https://fake-poi-api.mytaxi.com/?p2Lat=53.394655&p1Lon=9.757589&p1Lat=53.694865&p2Lon=10.099891
and this how I'm decoding it.
public extension Decodable {
static func parse(from item: Any?, strategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> Self? {
guard let data = self.data(from: item) else {
return nil
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = strategy
do {
let result = try decoder.decode(Self.self, from: data)
return result
} catch {
debugPrint(error)
return nil
}
}
private static func data(from item: Any?) -> Data? {
switch item {
case let data as Data:
return data
case let string as String:
return string.data(using: .utf8)
case .some(let item):
return try? JSONSerialization.data(withJSONObject: item, options: [])
case nil:
return nil
}
}
}
and the line to decode using the above function is
let model = LocationCarModel.parse(from: data)
Changing poiList to the standard swift array then no error occurs.
Any ideas how I can solve this issue? Any help would be appreciated. Thank you in advance.
You need to have your own init(from:) for the top struct since JSONDecoder doesn't understand NonEmpty and how to initialise it. Apart from the init I also added coding keys and a new error
enum DecodeError: Error {
case arrayIsEmptyError
}
struct LocationCarModel: Codable {
var poiList: NonEmpty<[PointOfInterest]>
enum CodingKeys: String, CodingKey {
case poiList
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let array = try container.decode([PointOfInterest].self, forKey: .poiList)
guard let first = array.first else {
throw DecodeError.arrayIsEmptyError
}
poiList = NonEmptyArray(first, array)
}
//...
}
You can try
struct Root: Codable {
let poiList: [PoiList]
}
// MARK: - PoiList
struct PoiList: Codable {
let id: Int
let coordinate: Coordinate
let fleetType: String
let heading: Double
}
// MARK: - Coordinate
struct Coordinate: Codable {
let latitude, longitude: Double
}
let res = try? JSONDecoder().decode(Root.self,from:data)
print(res)