How to convert dinamically Swift classes to JSON and vice-versa? - json

In my new project that act like a RPC, in some moment i receive a JSON with function name and a list of parameters. Example:
{
"f": "sample.login",
"p": [
{"n": "param1", "v": "value1"},
{"n": "param2", "v": true},
{"n": "param3", "v": {"id": 1, "title": "title xyz"}}
[...] any amount of params [...]
]
}
In other moments, i need create the same structure and encode as JSON. Example:
public class Param: Codable {
public var n: String
public var v: Any?
init(n: String, v: Any?) {
self.n = n
self.v = v
}
}
struct Todo: Codable {
var id: Int64
var title: String
var data: [String: String]
var done: Bool
}
public class JsonSerializer: Serializer {
private var decoder = JSONDecoder()
private var encoder = JSONEncoder()
public func encodeRequest(functionName: String, params: [Param]) -> String {
do {
let request = JsonRequestData(f: functionName, p: params)
let data = try encoder.encode(request)
if let result = String(data: data, encoding: .utf8) {
return result
} else {
print("[JsonSerializer : encodeRequest] Error when try to encode data")
}
} catch let e {
print("[JsonSerializer : encodeRequest] Error when try to encode data: \(e.localizedDescription)")
}
return ""
}
struct JsonRequestData: Codable {
let f: String
var p: [Param]
init(f: String, p: [Param]) {
self.f = f
self.p = p
}
}
}
let todo = Todo(id: 1, title: "Title 1", data: [:], done: true)
let name = "sample.todo.single"
var params: [Param] = []
params.append(Param(n: "suffix", v: "%"))
params.append(Param(n: "p2", v: todo))
let s = JsonSerializer()
let json = s.encodeRequest(functionName: name, params: params)
print(json)
I made it work in C++ (nlohmann json) and Kotlin (with gson). Only left make it work in Swift.
I know of course Swift doesn't support encoding ANY type. And I'm aware of some limitations on this in Swift.
But I would like to find a plausible solution to my problem.
Even if the user has to implement a protocol on his side for his types, or enter his type in a list of known types or something.
The project is at this URL, if you want to see the codes in more depth:
https://github.com/xplpc/xplpc
Removing this lock, the code is practically ready.
I tried on Apple forums, search on Google and on iOS group inside Slack.

Thanks for answers.
But after try a lot, i decide to use AnyCodable project (https://github.com/Flight-School/AnyCodable) with my modifications (https://github.com/xplpc/xplpc/tree/main/swift/lib/Sources).
AnyCodable let me use all swift types and if i use these types on my class/struct it works without problems.
To use any custom type, only need add more lines on AnyEncodable and AnyDecodable class.
Thanks.

Related

JSON and Realm — do I have to maintain separate structs / classes?

When I receive JSON from an API call, when decoding it I have structs like this:
import Foundation
struct JSON: Codable {
var alpha: Alpha
var beta: Beta
var gamma: [Gamma]?
}
I want to save the JSON in my Realm database, to later use and traverse like JSON. It's my understanding that I can't just use the existing structs I have written, instead I have to rewrite a second (similar) class like this to use with Realm:
import Foundation
import RealmSwift
class RealmJSON: Object, Identifiable {
#Persisted (primaryKey: true) var id: ObjectId
#Persisted var alpha: RealmAlpha
#Persisted var beta: RealmBeta
#Persisted var gamma: RealmSwift.List<RealmGamma>?
override class func primaryKey() -> String? {
"id"
}
convenience init(id: ObjectId, alpha: RealmAlpha, beta: RealmBeta, gamma: RealmSwift.List<RealmGamma>?) {
self.init()
self.id = id
self.alpha = alpha
self.beta = beta
self.gamma = gamma
}
}
Obviously, this is inconvenient especially when dealing with large amounts of JSON. Moreover I want to use Swagger codegen to write the client code for me, but it kind of defeats the purpose if I then have to manually add the Realm classes manually.
Is this the only way for dealing with JSON and a Realm database, or am I missing something here?
EDIT: I realise a simple way is to store most of the JSON as a raw JSON string with properties to identify schema type / version. Then I can just fetch the correct schema I require and parse the rawJSON string with the existing JSON structs...
You can pass the json data directly to your objects. I can think of two ways.
The first way, conform to Codable.
class Dog: Object, Codable {
#Persisted var name: String
}
class Cat: Object, Codable {
#Persisted var name: String
}
class Kid: Object, Codable {
#Persisted var name: String
}
class Owner: Object, Codable {
#Persisted var name: String
#Persisted var dog: Dog?
#Persisted var cat: Cat?
#Persisted var kids: List<Kid>
}
Let's use the following method to make json data:
func makeData() -> Data {
let string = """
{
"name": "Tom",
"kids": [{"name": "Penelope"}, {"name": "Rob"}],
"cat": {"name": "Lilly"},
"dog": {"name": "Lucy"}
}
"""
return string.data(using: .utf8)!
}
Now we can create our objects:
func decodeOwner() {
let decoder = JSONDecoder()
let owner = try! decoder.decode(Owner.self, from: makeData())
print("Decoded:", owner)
}
Another way is to use JSONSerialization and use the result to pass to the value constructor:
extension Object {
convenience init(json: Data) throws {
let data = try JSONSerialization.jsonObject(with: json)
self.init(value: data)
}
}
func serializeOwner() {
let owner = try! Owner(json: makeData())
print("Serialization:", owner)
}

TypeMismatch error in filling List from JSON

i am trying to fill a list from JSON in SwiftUI, which has this format:
{
"books": [{
"id": "87",
"title": "2001 odissea nello spazio",
"author_id": null,
"author": "arthur c. clarke",
"editor_id": null,
"editor": "longanesi",
"price": "0.00",
"isbn": "",
"note": ""
}, ......]
}
i created this struct for the Book object:
struct Book: Decodable, Identifiable {
public var id: Int;
public var title: String;
public var isbn: String;
enum CodingKeys: String, CodingKey {
case id = "id";
case title = "title";
case isbn = "isbn";
}
}
then i created this class to get the remote json:
import Foundation
public class GetBooks: ObservableObject {
#Published var books = [Book]();
init() {
load();
}
func load() {
let url = URL(string: "https://www.mattepuffo.com/api/book/get.php")!;
URLSession.shared.dataTask(with: url) {
(data, response, error) in
do {
if let d = data {
let decodedLists = JSONDecoder();
decodedLists.keyDecodingStrategy = .convertFromSnakeCase;
let dec = try decodedLists.decode([Book].self, from: d);
DispatchQueue.main.async {
self.books = dec;
}
} else {
print("Non ci sono libri");
}
} catch {
print(error)
}
}.resume();
}
}
but i get an error: typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
I think I understand what the problem is, but I don't understand how to solve it.
in the sense that the problem is that the json starts with an object (books) and not with an array.
but I don't understand how I have to modify the code!
I also tried to modify this line in this way, getting the error you see in the comment:
let dec = try decodedLists.decode(Book.self, from: d);
DispatchQueue.main.async {
self.books = dec; // Cannot assign value of type 'Book' to type '[Book]'
}
Your problem is that your JSON is not an Array of Book.
You need an upper level struct:
struct BookList: Decodable {
let books : [Book]
}
and then decode this structure instead of the array:
let dec = try decodedLists.decode(BookList.self, from: d);
DispatchQueue.main.async {
self.books = dec.books;
}
There are two major issues in your code:
You are ignoring the root object of the JSON, the dictionary with key books. This causes the error.
The type of key id is a string, in JSON everything in double quotes is String.
Further you don't need CodingKeys if all struct member names match the JSON keys and if the struct members are not going to be modified declare them as constants (let). Finally this is Swift: No trailing objective-c-ish semicolons.
struct Root: Decodable {
public let books: [Book]
}
struct Book: Decodable, Identifiable {
public let id: String
public let title: String
public let isbn: String
}
let result = try decodedLists.decode(Root.self, from: d)
DispatchQueue.main.async {
self.books = result.books
}

Swift // Convert a String Json file to an enum case

I have the object below:
class Food {
var cal: Int
var displayName: String
var imgUrl: String
var dishType: DishType
init(cal: Int, displayName: String, imgUrl: String, dishType: DishType) {
self.cal = cal
self.displayName = displayName
self.imgUrl = imgUrl
self.dishType = dishtype
}
}
enum DishType {
case starter
case main
case desert
}
And this is a part of my Alamofire request:
if let cal = foodJson["cal"].int,
let displayName = foodJson["display_name"].string,
let dishType = foodJson["type"].string,
let imgUrl = foodJson["imgUrl"].string {
let food = Food(cal: cal, displayName: displayName, imgUrl: imgUrl, dishType: ??)
foods.append(food)
How can I convert the Json String "dishType" into a "DishType" type I created with the enum in order to correctly fill my instance of Food?
You might want to specify an associated value for your enum:
enum DishType: String {
case starter = "starter"
case main = "main"
case desert = "desert"
}
Or, more simply:
enum DishType: String {
case starter
case main
case desert
}
Then you can do:
dishType = DishType(rawValue: string)
e.g.
if let dishTypeString = foodJson["type"].string,
let dishType = DishType(rawValue: dishTypeString) {
...
}
Personally, if doing Swift 4, I'd retire SwiftyJSON and use the native JSONDecoder and declare your types to be Codable. (Note, we still need to define the DishType to have associated values, like above.)
For example, let's imagine your response was something like:
{
"foods": [{
"cal": 800,
"display_name": "Beef",
"imgUrl": "http://example.com/wheres_the_beef.jpg",
"dishType": "main"
},
{
"cal": 2000,
"display_name": "Chocolate cake",
"imgUrl": "http://example.com/yummy.jpg",
"dishType": "desert"
}
]
}
You could then define your types like so:
struct Food: Codable {
let cal: Int
let displayName: String
let imgUrl: String
let dishType: DishType
}
enum DishType: String, Codable {
case starter
case main
case desert
}
And then you can parse the response like so:
struct FoodsResponse: Codable {
let foods: [Food]
}
Alamofire.request(url)
.responseData { response in
switch response.result {
case .success(let data):
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let responseObject = try decoder.decode(FoodsResponse.self, from: data)
print(responseObject.foods)
} catch {
print(error)
}
case .failure(let error):
print(error)
}
}
This gets you completely out of the business of manually iterating through the results to map it to your objects.
Clearly, I assume your real response has more keys than just foods, so you'd add whatever fields you needed to FoodsResponse, but hopefully this illustrates the idea of letting JSONDecoder parse the JSON into your model structures automatically.
For more information about JSONDecoder and Codable types, see Encoding and Decoding Custom Types.
By the way, my example FoodResponse structure prompted some question why I didn't just assume the web service would return an array of Food objects. Let me explain my rationale.
A more typical structure for FoodsResponse in a web service response would be something like:
struct FoodsResponse: Codable {
let success: Bool
let error: String? // only supplied if `success` was `false`
let foods: [Food]? // only supplied if `success` was `true`
}
In this structure, this response object can handle success scenarios, like:
{
"success": true,
"foods": [...]
}
Or failures:
{
"success": false,
"error": "No data found"
}
I think it best to have a structure that includes some common success Boolean, e.g. success, that all well-formed responses include, and then have various properties that are filled in for successes or failures, respectively.

Mapping a JSON object to a Swift class/struct

I need to "replicate" an entiry which is returned from a remote web API service in JSON. It looks like this:
{
"field1": "some_id",
"entity_name" = "Entity1"
"field2": "some name",
"details1": [{
"field1": 11,
"field2": "some value",
"data": {
"key1": "value1",
"key2": "value2",
"key3": "value3",
// any other, unknown at compile time keys
}
}],
"details2": {
"field1": 13,
"field2": "some value2"
}
}
Here's my attempt:
struct Entity1 {
struct Details1 {
let field1: UInt32
let field2: String
let data: [String: String]
}
struct Details2 {
let field1: UInt32
let field2: String
}
let field1: String
static let entityName = "Entity1"
let field2: String
let details1: [Details1]
let details2: Details2
}
Is it a good idea to use structs instead of classes for such a goal
as mine?
Can I anyhow define a nested struct or a class, say
Details1 and create a variable of it at the same time?
Like this:
//doesn't compile
struct Entity1 {
let details1: [Details1 {
let field1: UInt32
let field2: String
let data: [String: String]
}]
You can use any if the following good open-source libraries available to handle the mapping of JSON to Object in Swift, take a look :
Mapper
ObjectMapper
JSONHelper
Argo
Unbox
Each one have nice a good tutorial for beginners.
Regarding the theme of struct or class, you can consider the following text from The Swift Programming Language documentation:
Structure instances are always passed by value, and class
instances are always passed by reference. This means that they are
suited to different kinds of tasks. As you consider the data
constructs and functionality that you need for a project, decide
whether each data construct should be defined as a class or as a
structure.
As a general guideline, consider creating a structure when one or more
of these conditions apply:
The structure’s primary purpose is to encapsulate a few relatively simple data values.
It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an
instance of that structure.
Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
The structure does not need to inherit properties or behavior from another existing type.
Examples of good candidates for structures include:
The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.
A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.
A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.
In all other cases, define a class, and create instances of that class
to be managed and passed by reference. In practice, this means that
most custom data constructs should be classes, not structures.
I hope this help you.
HandyJSON is exactly what you need. See code example:
struct Animal: HandyJSON {
var name: String?
var id: String?
var num: Int?
}
let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}"
if let animal = JSONDeserializer.deserializeFrom(json: jsonString) {
print(animal)
}
https://github.com/alibaba/handyjson
Details
Xcode 10.2.1 (10E1001), Swift 5
Links
Pods:
Alamofire - loading data
More info:
Codable
More samples of usage Codable and ObjectMapper in Swift 5
Task
Get itunes search results using iTunes Search API with simple request https://itunes.apple.com/search?term=jack+johnson
Full sample
import UIKit
import Alamofire
// Itunce api doc: https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/#searching
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
private func loadData() {
let urlString = "https://itunes.apple.com/search?term=jack+johnson"
Alamofire.request(urlString).response { response in
guard let data = response.data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(ItunceItems.self, from: data)
print(result)
} catch let error {
print("\(error.localizedDescription)")
}
}
}
}
struct ItunceItems: Codable {
let resultCount: Int
let results: [ItunceItem]
}
struct ItunceItem: Codable {
var wrapperType: String?
var artistId: Int?
var trackName: String?
var trackPrice: Double?
var currency: String?
}
you could use SwiftyJson and let json = JSONValue(dataFromNetworking)
if let userName = json[0]["user"]["name"].string{
//Now you got your value
}
Take a look at this awesome library that perfectly fits your need, Argo on GitHub.
In your case, a struct is ok. You can read more on how to choose between a struct and a class here.
You can go with this extension for Alamofire https://github.com/sua8051/AlamofireMapper
Declare a class or struct:
class UserResponse: Decodable {
var page: Int!
var per_page: Int!
var total: Int!
var total_pages: Int!
var data: [User]?
}
class User: Decodable {
var id: Double!
var first_name: String!
var last_name: String!
var avatar: String!
}
Use:
import Alamofire
import AlamofireMapper
let url1 = "https://raw.githubusercontent.com/sua8051/AlamofireMapper/master/user1.json"
Alamofire.request(url1, method: .get
, parameters: nil, encoding: URLEncoding.default, headers: nil).responseObject { (response: DataResponse<UserResponse>) in
switch response.result {
case let .success(data):
dump(data)
case let .failure(error):
dump(error)
}
}

How to serialize or convert Swift objects to JSON?

This below class
class User: NSManagedObject {
#NSManaged var id: Int
#NSManaged var name: String
}
Needs to be converted to
{
"id" : 98,
"name" : "Jon Doe"
}
I tried manually passing the object to a function which sets the variables into a dictionary and returns the dictionary. But I would want a better way to accomplish this.
In Swift 4, you can inherit from the Codable type.
struct Dog: Codable {
var name: String
var owner: String
}
// Encode
let dog = Dog(name: "Rex", owner: "Etgar")
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(dog)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
// Decode
let jsonDecoder = JSONDecoder()
let secondDog = try jsonDecoder.decode(Dog.self, from: jsonData)
Along with Swift 4 (Foundation) now it is natively supported in both ways, JSON string to an object - an object to JSON string.
Please see Apple's documentation here JSONDecoder() and here JSONEncoder()
JSON String to Object
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let myStruct = try! decoder.decode(myStruct.self, from: jsonData)
Swift Object to JSONString
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(myStruct)
print(String(data: data, encoding: .utf8)!)
You can find all details and examples here Ultimate Guide to JSON Parsing With Swift 4
UPDATE: Codable protocol introduced in Swift 4 should be sufficient for most of the JSON parsing cases. Below answer is for people who are stuck in previous versions of Swift and for legacy reasons
EVReflection :
This works of reflection principle. This takes less code and also supports NSDictionary, NSCoding, Printable, Hashable and Equatable
Example:
class User: EVObject { # extend EVObject method for the class
var id: Int = 0
var name: String = ""
var friends: [User]? = []
}
# use like below
let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
let user = User(json: json)
ObjectMapper :
Another way is by using ObjectMapper. This gives more control but also takes a lot more code.
Example:
class User: Mappable { # extend Mappable method for the class
var id: Int?
var name: String?
required init?(_ map: Map) {
}
func mapping(map: Map) { # write mapping code
name <- map["name"]
id <- map["id"]
}
}
# use like below
let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
let user = Mapper<User>().map(json)
I worked a bit on a smaller solution that doesn't require inheritance. But it hasn't been tested much. It's pretty ugly atm.
https://github.com/peheje/JsonSerializerSwift
You can pass it into a playground to test it. E.g. following class structure:
//Test nonsense data
class Nutrient {
var name = "VitaminD"
var amountUg = 4.2
var intArray = [1, 5, 9]
var stringArray = ["nutrients", "are", "important"]
}
class Fruit {
var name: String = "Apple"
var color: String? = nil
var weight: Double = 2.1
var diameter: Float = 4.3
var radius: Double? = nil
var isDelicious: Bool = true
var isRound: Bool? = nil
var nullString: String? = nil
var date = NSDate()
var optionalIntArray: Array<Int?> = [1, 5, 3, 4, nil, 6]
var doubleArray: Array<Double?> = [nil, 2.2, 3.3, 4.4]
var stringArray: Array<String> = ["one", "two", "three", "four"]
var optionalArray: Array<Int> = [2, 4, 1]
var nutrient = Nutrient()
}
var fruit = Fruit()
var json = JSONSerializer.toJson(fruit)
print(json)
prints
{"name": "Apple", "color": null, "weight": 2.1, "diameter": 4.3, "radius": null, "isDelicious": true, "isRound": null, "nullString": null, "date": "2015-06-19 22:39:20 +0000", "optionalIntArray": [1, 5, 3, 4, null, 6], "doubleArray": [null, 2.2, 3.3, 4.4], "stringArray": ["one", "two", "three", "four"], "optionalArray": [2, 4, 1], "nutrient": {"name": "VitaminD", "amountUg": 4.2, "intArray": [1, 5, 9], "stringArray": ["nutrients", "are", "important"]}}
This is not a perfect/automatic solution but I believe this is the idiomatic and native way to do such. This way you don't need any libraries or such.
Create an protocol such as:
/// A generic protocol for creating objects which can be converted to JSON
protocol JSONSerializable {
private var dict: [String: Any] { get }
}
extension JSONSerializable {
/// Converts a JSONSerializable conforming class to a JSON object.
func json() rethrows -> Data {
try JSONSerialization.data(withJSONObject: self.dict, options: nil)
}
}
Then implement it in your class such as:
class User: JSONSerializable {
var id: Int
var name: String
var dict { return ["id": self.id, "name": self.name] }
}
Now:
let user = User(...)
let json = user.json()
Note: if you want json as a string, it is very simply to convert to a string: String(data: json, encoding .utf8)
Some of the above answers are completely fine, but I added an extension here, just to make it much more readable and usable.
extension Encodable {
var convertToString: String? {
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
do {
let jsonData = try jsonEncoder.encode(self)
return String(data: jsonData, encoding: .utf8)
} catch {
return nil
}
}
}
struct User: Codable {
var id: Int
var name: String
}
let user = User(id: 1, name: "name")
print(user.convertToString!)
//This will print like the following:
{
"id" : 1,
"name" : "name"
}
Not sure if lib/framework exists, but if you would like to do it automatically and you would like to avoid manual labour :-) stick with MirrorType ...
class U {
var id: Int
var name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
extension U {
func JSONDictionary() -> Dictionary<String, Any> {
var dict = Dictionary<String, Any>()
let mirror = reflect(self)
var i: Int
for i = 0 ; i < mirror.count ; i++ {
let (childName, childMirror) = mirror[i]
// Just an example how to check type
if childMirror.valueType is String.Type {
dict[childName] = childMirror.value
} else if childMirror.valueType is Int.Type {
// Convert to NSNumber for example
dict[childName] = childMirror.value
}
}
return dict
}
}
Take it as a rough example, lacks proper conversion support, lacks recursion, ... It's just MirrorType demonstration ...
P.S. Here it's done in U, but you're going to enhance NSManagedObject and then you'll be able to convert all NSManagedObject subclasses. No need to implement this in all subclasses/managed objects.
struct User:Codable{
var id:String?
var name:String?
init(_ id:String,_ name:String){
self.id = id
self.name = name
}
}
Now just make your object like this
let user = User("1","pawan")
do{
let userJson = try JSONEncoder().encode(parentMessage)
}catch{
fatalError("Unable To Convert in Json")
}
Then reconvert from json to Object
let jsonDecoder = JSONDecoder()
do{
let convertedUser = try jsonDecoder.decode(User.self, from: userJson.data(using: .utf8)!)
}catch{
}
2021 | SWIFT 5.1 | Results solution
Input data:
struct ConfigCreds: Codable {
// some params
}
usage:
// get JSON from Object
configCreds
.asJson()
.onSuccess{ varToSaveJson = $0 }
.onFailure{ _ in // any failure code }
// get object of type "ConfigCreds" from JSON
someJsonString
.decodeFromJson(type: ConfigCreds.self)
.onSuccess { configCreds = $0 }
.onFailure{ _ in // any failure code }
Back code:
#available(macOS 10.15, *)
public extension Encodable {
func asJson() -> Result<String, Error>{
JSONEncoder()
.try(self)
.flatMap{ $0.asString() }
}
}
public extension String {
func decodeFromJson<T>(type: T.Type) -> Result<T, Error> where T: Decodable {
self.asData()
.flatMap { JSONDecoder().try(type, from: $0) }
}
}
///////////////////////////////
/// HELPERS
//////////////////////////////
#available(macOS 10.15, *)
fileprivate extension JSONEncoder {
func `try`<T : Encodable>(_ value: T) -> Result<Output, Error> {
do {
return .success(try self.encode(value))
} catch {
return .failure(error)
}
}
}
fileprivate extension JSONDecoder {
func `try`<T: Decodable>(_ t: T.Type, from data: Data) -> Result<T,Error> {
do {
return .success(try self.decode(t, from: data))
} catch {
return .failure(error)
}
}
}
fileprivate extension String {
func asData() -> Result<Data, Error> {
if let data = self.data(using: .utf8) {
return .success(data)
} else {
return .failure(WTF("can't convert string to data: \(self)"))
}
}
}
fileprivate extension Data {
func asString() -> Result<String, Error> {
if let str = String(data: self, encoding: .utf8) {
return .success(str)
} else {
return .failure(WTF("can't convert Data to string"))
}
}
}
fileprivate func WTF(_ msg: String, code: Int = 0) -> Error {
NSError(code: code, message: msg)
}