Realm swift list using Decodable - swift

I am trying figure out how could I pars Realm list using new feature in Swift 4, Decodable protocol.
Here is a example JSON:
[{
"name": "Jack",
"lastName": "Sparrow",
"number": "1",
"address": [
{
"city": "New York",
"street": "av. test"
}
]
},
{
"name": "Cody",
"lastName": "Black",
"number": "2"
},
{
"name": "Name",
"lastName": "LastName",
"number": "4",
"address": [
{
"city": "Berlin",
"street": "av. test2"
},
{
"city": "Minsk",
"street": "av. test3"
}
]
}]
And Realm Models:
Person
public final class Person: Object, Decodable {
#objc dynamic var name = ""
#objc dynamic var lastName = ""
var address = List<Place>()
override public static func primaryKey() -> String? {
return "lastName"
}
private enum CodingKeys: String, CodingKey { case name, lastName, address}
convenience public init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.lastName = try container.decode(String.self, forKey: .lastName)
self.address = try container.decodeIfPresent(List<Place>.self, forKey: .address) ?? List()
}
}
Place
public final class Place: Object, Decodable {
#objc dynamic var city = ""
#objc dynamic var street = 0
override public static func primaryKey() -> String? {
return "street"
}
// We dont need to implement coding keys becouse there is nothing optional and the model is not expanded by extra properties.
}
And the result of parsing this JSON would be:
[Person {
name = Jack;
lastName = Sparrow;
number = 1;
address = List<Place> <0x6080002496c0> (
);
}, Person {
name = Cody;
lastName = Black;
number = 2;
address = List<Place> <0x6080002496c0> (
);
}, Person {
name = Name;
lastName = LastName;
number = 4;
address = List<Place> <0x6080002496c0> (
);
As we can see our list are always empty.
self.address = try container.decodeIfPresent(List<Place>.self, forKey: .address) ?? List()
will always be a nil.
Also I am extending List by :
extension List: Decodable {
public convenience init(from decoder: Decoder) throws {
self.init()
}
}
Any ideas what might be wrong ?
EDIT
struct LoginJSON: Decodable {
let token: String
let firstCustomArrayOfObjects: [FirstCustomArrayOfObjects]
let secondCustomArrayOfObjects: [SecondCustomArrayOfObjects]
let preferences: Preferences
let person: [Person]
}
Each property (instead of token) is a type of Realm Object and the last one is the one from above.
Thanks!

You cannot go directly from your JSON to a List. What's in the JSON is an array. So this line won't work:
self.address = try container.decodeIfPresent(List<Place>.self, forKey: .address) ?? List()
You have to start by fetching the array:
if let arr = try container.decodeIfPresent(Array<Place>.self, forKey: .address) {
// arr is now an array of Place
self.address = // make a List from `arr`, however one does that
} else {
self.address = nil
}

Related

swift can not make struct for parsing geoJSON

I have geoJSON file:
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
40.303141,
55.9765684
],
[
40.3033449,
55.9765114
],
[
40.3034017,
55.976575
],
[
40.3031979,
55.9766321
],
[
40.303141,
55.9765684
]
]
]
]
},
"properties": {
"#id": 4305947573,
"building": "yes"
}
}
I'm interested in properties:
"properties":{"#id":4305947573,"building":"yes"}
I want parse "properties", and make structure:
struct Feature: Decodable {
let type: String
let properties: Dictionary<String, String> }
It's work good, but then i add parameter in geoJSON: "#id":4305947573
4305947573 - this is Int variable, and parser don't parse geoJSON.
I think i need modify my struct Feature. I want to parser understand and String, and Int in properties.
Help me please. Thank you
There are a number of GeoJSON swift libraries (search github) that you could use
instead of re-inventing the wheel.
If you really want to code it yourself, try this approach,
where the dynamic keys and values of properties are decoded
into a dictionary of var data: [String: Any], as shown.
Use the struct models like this:
let result = try JSONDecoder().decode(Feature.self, from: data)
Models
struct Feature: Decodable {
let type: String
var properties: Properties
// ...
}
struct Properties: Decodable {
var data: [String: Any] = [:]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicKey.self)
container.allKeys.forEach { key in
if let theString = try? container.decode(String.self, forKey: key) {
self.data[key.stringValue] = theString
}
if let theInt = try? container.decode(Int.self, forKey: key) {
self.data[key.stringValue] = theInt
}
}
}
}
struct DynamicKey: CodingKey {
var intValue: Int?
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = ""
}
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
}

Issue with Enum values with Space | Swift

Essentially I am decoding a JSON object with keys that could only be a few different values.
struct People: Decodable {
var name: String
var grade: String
var code: PersonID
enum PersonCodes: String, Decodable {
case In_Transit = "0",
Accepted = "1",
Exception = "2",
Delivered = "3"
}
}
The codes values in the JSON are numbers presented as Strings like "0", "1", "2" etc..
Each code has a meaning like In_Transit, Hired, Ready, All Set .. how can codes be outputed with spaces if enums does not allow spaces (I need to replace the _ with space).
Example of JSON:
{
"name" : "Jake",
"grade" : "A Grade"
"code" : "0"
}
Need for code 0 to be read as "In Transit"
you could try this approach (works for me):
struct People: Decodable {
var name: String
var grade: String
var code: PersonCodes
enum CodingKeys: String, CodingKey {
case name, grade, code
}
enum PersonCodes: String, Decodable {
case In_Transit = "0",
Accepted = "1",
Exception = "2",
Delivered = "3"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
grade = try container.decode(String.self, forKey: .grade)
let stringCode = try container.decode(String.self, forKey: .code)
if let dasCode = PersonCodes(rawValue: stringCode) {
code = dasCode
} else {
code = PersonCodes.In_Transit // <-- todo, pick a default
}
}
}
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear {
let json = """
{
"name" : "Jake",
"grade" : "A Grade",
"code" : "1"
}
"""
let data = json.data(using: .utf8)!
do {
let decoded = try JSONDecoder().decode(People.self, from: data)
print(decoded)
print(decoded.code)
print(decoded.code.rawValue)
} catch {
print("\(error)")
}
}
}
}
You could also use this, if you want a string description of the code (which is a String not an Int in the json data):
struct People: Decodable {
var name: String
var grade: String
var code: String
enum CodingKeys: String, CodingKey {
case name, grade, code
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
grade = try container.decode(String.self, forKey: .grade)
let stringCode = try container.decode(String.self, forKey: .code)
switch stringCode {
case "0": code = "In Transit"
case "1": code = "Accepted"
case "2": code = "Exception"
case "3": code = "Delivered"
default: code = "Unknown" // <-- todo default
}
}
}

How to decode JSON array as property of struct with JSONDecoder

I had data struct like this
{
"version": 1,
"profile": [
{
"type": "name",
"value": "Hellow"
},
{
"type": "email",
"value": "1#a.com"
},
]
}
Now I could decode it like this
struct Profile: Decodable {
let version: Int
let name: String
let email: String
struct Item: Decodable {
let type: String
let value: String
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
version = try container.decode(Int.self, forKey: .version)
let items = try container.decode([Item].self, forKey: .data)
name = items.first{$0.type == "name"}!.value
email = items.first{$0.type == "email"}!.value
}
}
Does there are any way to update Profile without rewrite init(from)?
Update
#dynamicMemberLookup can use like mapping function.
But it will lose autocomplete.
#dynamicMemberLookup
struct Profile: Decodable {
let version: Int
struct Item: Decodable {
let type: Keys
let value: String
}
let profile: [Item]
enum Keys: String, Decodable, ExpressibleByStringLiteral {
case name
case email
case undefined
init(stringLiteral: String) {
self = Keys(rawValue: stringLiteral) ?? .undefined
}
}
subscript(dynamicMember member: Keys) -> String {
return profile.first{$0.type == member}?.value ?? "undefined"
}
}

How to model a Swift dictionary property on a Realm object?

How should I model a dictionary property on a Realm object so when encoded to JSON I can get this:
{
"firstName": "John",
"lastName": "Doe",
"favoriteThings": {
"car": "Audi R8",
"fruit": "strawberries",
"tree": "Oak"
}
}
I tried creating a new Object FavoriteThings with 'key' and 'value' properties as I've seen elsewhere...
public class Person: Object {
#objc dynamic var firstName = ""
#objc dynamic var lastName = ""
var favoriteThings = List<FavoriteThings>()
}
But List gives me an array, naturally, when I encode it to JSON. I don't want an array. I'm using Swift Codable.
{
"firstName": "John",
"lastName": "Doe",
"favoriteThings": [
{
"key": "fruit",
"value": "strawberries"
},
{
"key": "tree",
"value": "Oak"
}
],
}
Any pointers appreciated!
Gonzalo
As you know, Lists are encoded into json arrays by default. So, to encode a List into a Dictionary you'll have to implement a custom encode method to do exactly that.
public class FavoriteThings: Object {
#objc dynamic var key = ""
#objc dynamic var value = ""
convenience init(key: String, value: String) {
self.init()
self.key = key
self.value = value
}
}
public class Person: Object, Encodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
case favoriteThings
}
#objc dynamic var firstName = ""
#objc dynamic var lastName = ""
let favoriteThings = List<FavoriteThings>()
convenience init(firstName: String, lastName: String, favoriteThings: [FavoriteThings]) {
self.init()
self.firstName = firstName
self.lastName = lastName
self.favoriteThings.append(objectsIn: favoriteThings)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
var favThings: [String: String] = [:]
for thing in favoriteThings {
favThings[thing.key] = thing.value
}
try container.encode(favThings, forKey: .favoriteThings)
}
}
And the usage would be like this:
func testEncode() {
let john = Person(
firstName: "John",
lastName: "Doe",
favoriteThings: [
.init(key: "car", value: "Audi R8"),
.init(key: "fruit", value: "strawberries"),
.init(key: "tree", value: "Oak"),
])
let encoder = JSONEncoder()
let data = try! encoder.encode(john)
if let string = String(data: data, encoding: .utf8) {
print(string)
}
}
Which prints:
{"firstName":"John","favoriteThings":{"car":"Audi R8","fruit":"strawberries","tree":"Oak"},"lastName":"Doe"}

Codable. How decode dictionary to property [duplicate]

How does the Swift 4 Decodable protocol cope with a dictionary containing a key whose name is not known until runtime? For example:
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
Here we have an array of dictionaries; the first has keys categoryName and Trending, while the second has keys categoryName and Comedy. The value of the categoryName key tells me the name of the second key. How do I express that using Decodable?
The key is in how you define the CodingKeys property. While it's most commonly an enum it can be anything that conforms to the CodingKey protocol. And to make dynamic keys, you can call a static function:
struct Category: Decodable {
struct Detail: Decodable {
var category: String
var trailerPrice: String
var isFavorite: Bool?
var isWatchlist: Bool?
}
var name: String
var detail: Detail
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
static let name = CodingKeys.make(key: "categoryName")
static func make(key: String) -> CodingKeys {
return CodingKeys(stringValue: key)!
}
}
init(from coder: Decoder) throws {
let container = try coder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.detail = try container.decode([Detail].self, forKey: .make(key: name)).first!
}
}
Usage:
let jsonData = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
}
]
""".data(using: .utf8)!
let categories = try! JSONDecoder().decode([Category].self, from: jsonData)
(I changed isFavourit in the JSON to isFavourite since I thought it was a mispelling. It's easy enough to adapt the code if that's not the case)
You can write a custom struct that functions as a CodingKeys object, and initialize it with a string such that it extracts the key you specified:
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
Thus, once you know what the desired key is, you can say (in the init(from:) override:
let key = // whatever the key name turns out to be
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
So what I ended up doing is making two containers from the decoder — one using the standard CodingKeys enum to extract the value of the "categoryName" key, and another using the CK struct to extract the value of the key whose name we just learned:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here, then, is my entire Decodable struct:
struct ResponseData : Codable {
let categoryName : String
let unknown : [Inner]
struct Inner : Codable {
let category : String
let trailerPrice : String
let isFavourit : String?
let isWatchList : String?
}
private enum CodingKeys : String, CodingKey {
case categoryName
}
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
}
And here's the test bed:
let json = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
"""
let myjson = try! JSONDecoder().decode(
[ResponseData].self,
from: json.data(using: .utf8)!)
print(myjson)
And here's the output of the print statement, proving that we've populated our structs correctly:
[JustPlaying.ResponseData(
categoryName: "Trending",
unknown: [JustPlaying.ResponseData.Inner(
category: "Trending",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)]),
JustPlaying.ResponseData(
categoryName: "Comedy",
unknown: [JustPlaying.ResponseData.Inner(
category: "Comedy",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)])
]
Of course in real life we'd have some error-handling, no doubt!
EDIT Later I realized (in part thanks to CodeDifferent's answer) that I didn't need two containers; I can eliminate the CodingKeys enum, and my CK struct can do all the work! It is a general purpose key-maker:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CK.self)
self.categoryName = try! con.decode(String.self, forKey:CK(stringValue:"categoryName")!)
let key = self.categoryName
self.unknown = try! con.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here's what I eventually came up for this json:
let json = """
{
"BTC_BCN":{
"last":"0.00000057",
"percentChange":"0.03636363",
"baseVolume":"47.08463318"
},
"BTC_BELA":{
"last":"0.00001281",
"percentChange":"0.07376362",
"baseVolume":"5.46595029"
}
}
""".data(using: .utf8)!
We make such a structure:
struct Pair {
let name: String
let details: Details
struct Details: Codable {
let last, percentChange, baseVolume: String
}
}
then decode:
if let pairsDictionary = try? JSONDecoder().decode([String: Pair.Details].self, from: json) {
var pairs: [Pair] = []
for (name, details) in pairsDictionary {
let pair = Pair(name: name, details: details)
pairs.append(pair)
}
print(pairs)
}
It is also possible to call not pair.details.baseVolume, but pair.baseVolume:
struct Pair {
......
var baseVolume: String { return details.baseVolume }
......
Or write custom init:
struct Pair {
.....
let baseVolume: String
init(name: String, details: Details) {
self.baseVolume = details.baseVolume
......