Returning specific field in vapor4 - fluent

I don't want to get the location field, but return the other fields. How would I make it happen?
func getperinfo(_ req: Request) throws -> EventLoopFuture<[Info]>{
let user = try req.auth.require(User.self)
return Info.query(on: req.db).filter(\.$user.$id == user.id!).all()
}
Model:
import Foundation
import Fluent
import Vapor
import FluentPostgresDriver
import FluentPostGIS
final class Info:Model,Content{
static let schema = "info"
#ID(key: .id)
var id:UUID?
#Field(key: "姓名")
var name: String
#Field(key: "IG帳號")
var account: String
#Field(key: "頭像")
var picture: String
#Field(key: "年紀")
var age: String
#Field(key: "生日")
var birth: String
#Field(key: "居住城市")
var city: String
#Field(key: "興趣")
var hobby : String
#Field(key:"位置")
var location: GeometricPoint2D
#Parent(key: "user_id")
var user: User
init(){}
init(id:UUID?=nil, name:String, account:String, picture:String ,age:String, birth:String,location: GeometricPoint2D , city:String, hobby:String, userId:UUID){
self.id=id
self.name=name
self.account=account
self.picture=picture
self.age=age
self.birth=birth
self.location=location
self.city=city
self.hobby=hobby
self.$user.id=userId
}
}

You just could create a new struct with less fields and then map results into it
struct MyCustomResult: Content {
let id: UUID
let name: String
}
func getperinfo(_ req: Request) throws -> EventLoopFuture<[MyCustomResult]>{
let user = try req.auth.require(User.self)
return Info.query(on: req.db).filter(\.$user.$id == user.id!).all().map {
$0.map { MyCustomResult(id: $0.id!, name: $0.name) }
}
}

Related

Parse JSON with swiftyJSON and more

Hi I've tried to parse my JSON but I couldn't get my data from it,
(I used SwiftyJSON)
how can I parse this ugly JSON?
//Mark: parser functions:
private func parseProvincesResult(provincesJSON: JSON, completion: #escaping(_ :ProvincesV2) -> Void) {
print(provincesJSON)
let errorCode: Int = provincesJSON["ErrorCode"].intValue
let errorDescriptions: String = provincesJSON["ErrorString"].stringValue
let newMacKey: String = provincesJSON["NewMacKey"].stringValue
let newPinKey: String = provincesJSON["NewPinKey"].stringValue
let version: Int = provincesJSON["Ver"].intValue
var provinceList: [ProvinceListResult] = []
for i in provincesJSON["ProvinceListResult"].arrayValue {
let id: Int = i["Id"].intValue
let name: String = i["Name"].stringValue
let proList = ProvinceListResult(id: id, name: name)
provinceList.append(proList)
}
let model = ProvincesV2(errorCode: errorCode, errorDescriptions: errorDescriptions, newMacKey: newMacKey, newPinKey: newPinKey, version: version, provinceList: provinceList)
completion(model)
}
and my JSON is:
{"ErrorCode":"8",
"ErrorString":"عملیات با موفقیت انجام شد.",
"NewMacKey":"vph+eLFgxa6LVq90QfsNUA==",
"NewPinKey":"evJiM9W6S9RWEClR6csxEQ==",
"Ver":201,
"ProvinceListResult":[{"Id":1,"Name":"آذربايجان شرقي"},
{"Id":2,"Name":"آذربايجان غربي"},
{"Id":3,"Name":"اردبيل"},
{"Id":4,"Name":"اصفهان"},
{"Id":5,"Name":"البرز"},
{"Id":6,"Name":"ايلام"},
{"Id":7,"Name":"بوشهر"},
{"Id":8,"Name":"تهران"},
{"Id":9,"Name":"چهارمحال و بختياري"},
{"Id":10,"Name":"خراسان جنوبي"},{"Id":11,"Name":"خراسان رضوي"},{"Id":12,"Name":"خراسان شمالي"},{"Id":13,"Name":"خوزستان"},{"Id":14,"Name":"زنجان"},{"Id":15,"Name":"سمنان"},{"Id":16,"Name":"سيستان و بلوچستان"},{"Id":17,"Name":"فارس"},{"Id":18,"Name":"قزوين"},{"Id":19,"Name":"قم"},{"Id":20,"Name":"کردستان"},{"Id":21,"Name":"کرمان"},{"Id":22,"Name":"کرمانشاه"},{"Id":23,"Name":"کهکيلويه و بويراحمد"},{"Id":24,"Name":"گلستان"},{"Id":25,"Name":"گيلان"},{"Id":26,"Name":"لرستان"},{"Id":27,"Name":"مازندران"},{"Id":28,"Name":"مرکزي"},{"Id":29,"Name":"هرمزگان"},{"Id":30,"Name":"همدان"},{"Id":31,"Name":"يزد"},{"Id":44,"Name":"کیش"}]}
how can I parse It?
tnx
Using Codable, you could do this:
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let errorCode, errorString, newMACKey, newPinKey: String
let ver: Int
let provinceListResult: [ProvinceListResult]
enum CodingKeys: String, CodingKey {
case errorCode = "ErrorCode"
case errorString = "ErrorString"
case newMACKey = "NewMacKey"
case newPinKey = "NewPinKey"
case ver = "Ver"
case provinceListResult = "ProvinceListResult"
}
}
// MARK: - ProvinceListResult
struct ProvinceListResult: Codable {
let id: Int
let name: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
}
}
(generated by https://app.quicktype.io)
And getting a value out might look like:
let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
print(welcome?.provinceListResult[1])
print(welcome?.provinceListResult[1])
If your original data is in String form, it can be converted to Data by doing this:
myStringJSON.data(using: .utf8)!
create two models for parse JSON
first for the province:
public struct ProvinceModel {
var id: Int?
var name: String?
init(json: JSON) {
self.id = json["Id"].int
self.name = json["Name"].string
}
}
with a constructor, you can parse JSON and set data to variables.
and second model:
public struct MyJSONModel {
var errorString: String?
var newMacKey: String?
var newPinKey: String?
var Ver: Int?
var Provinces: [ProvinceModel]?
init(json: JSON) {
// simple parse
self.errorString = json["ErrorCode"].string
self.newMacKey = json["NewMacKey"].string
self.newPinKey = json["NewPinKey"].string
self.Ver = json["Ver"].int
// array
self.Provinces = [] // empty array
if let jsonArray = json["ProvinceListResult"].array {
for json in jsonArray {
let ProvinceObject = ProvinceModel.init(json: json)
self.Provinces?.append(ProvinceObject)
}
}
}
}
for parse province items in the array, you must create a loop and create an object then set in variables
I prefer to use a constructor for parse JSON instead of a custom function.

Return Children and Siblings values in response

It's probably a trivial question, but I couldn't solve it and didn't find the answer.
I have 3 tables related to each other as Parent-Child and Siblings. And I want to return some values form all of the tables and the same JSON response.
final class ClimbingGym: Model, Content {
static let schema = "climbing_gyms"
#ID(key: .id)
var id: UUID?
#Field(key: "name")
var name: String
#Field(key: "description")
var description: String
#Siblings(through: ClimbingDisciplineClimbingGym.self, from: \.$gym, to: \.$discipline)
var disciplines: [ClimbingDiscipline]
#Children(for: \.$gym)
var socialNetworks: [SocialNetwork]
init() { }
init(
id: UUID? = nil,
name: String,
description: String,
) {
self.id = id
self.name = name
self.description = description
}
}
enum ClimbingDisciplineType: String, Codable, CaseIterable, Equatable {
static let schema = "climbing_discipline_type"
case lead
case boulder
case speed
}
final class ClimbingDiscipline: Model, Content {
static let schema = "climbing_disciplines"
#ID(key: .id)
var id: UUID?
#Enum(key: "type")
var type: ClimbingDisciplineType
init() { }
init(
id: UUID? = nil,
type: ClimbingDisciplineType
) {
self.id = id
self.type = type
}
}
final class SocialNetwork {
static let schema = "social_networks"
#ID(key: .id)
var id: UUID?
#Field(key: "link")
var link: String
#Parent(key: "gym_id")
var gym: ClimbingGym
init() { }
init(
id: UUID? = nil,
link: String,
gym: ClimbingGym
) throws {
self.id = id
self.link = link
self.$gym.id = try gym.requireID()
}
}
And I want to return that model:
struct ClimbingGymResponse: Codable, Content {
let id: UUID
let name: String
let description: String
let disciplines: [ClimbingDisciplineType]
let socialNetworks: [String]
}
so the query that I'm using now looks like that
func getAll(req: Request) throws -> EventLoopFuture<[ClimbingGymResponse]> {
ClimbingGym
.query(on: req.db)
.join(children: \ClimbingGym.$socialNetworks)
.join(siblings: \ClimbingGym.$disciplines)
.all()
}
and it obviously doesn't work, because it returns [ClimbingGym] instead of [ClimbingGymResponse].
So how can I transform one to another?
I have problems with filling disciplines: [ClimbingDisciplineType] and socialNetworks: [String] field for each gym
Thank you!
You can map the array of results into the type you want. So
ClimbingGym
.query(on: req.db)
.with(\.$socialNetworks)
.with(\.$disciplines)
.all().flatMapThrowing { gyms in
try gyms.map { try ClimbingGymResponse(id: $0.requireID(), ...) }
}

JSON decoder The data couldn’t be read because it isn’t in the correct format

I am new to this. Somehow I am able to understand how to do this.
I am doing below, but it's giving error- The data couldn’t be read because it isn’t in the correct format.Can someone help me with this? I am stuck on this from past 4 days. I really appreciate.
import SwiftUI
import Foundation
import Combine
struct Movie: Decodable, Identifiable {
var id: Int
var video: String
var vote_count: String
var vote_average: String
var title: String
var release_date: String
var original_language: String
var original_title: String
}
struct MovieList: Decodable{
var results: [Movie]
___________
class NetworkingManager : ObservableObject{
var objectWillChange = PassthroughSubject<NetworkingManager, Never>()
#Published var movies = [Movie]()
init() {
load()
}
func load(){
let url = URL(string: "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=<HIDDEN>")!
URLSession.shared.dataTask(with: url){ (data, response, error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([Movie].self, from: d)
DispatchQueue.main.async {
self.movies = decodedLists
}
}else {
print("No Data")
}
} catch {
print (error.localizedDescription)
}
}.resume()
}
}
This is how the response looks like:
{
"page": 1,
"results": [
{
"id": 419704,
"video": false,
"vote_count": 1141,
"vote_average": 6.2,
"title": "Ad Astra",
"release_date": "2019-09-17",
"original_language": "en",
"original_title": "Ad Astra",
"genre_ids": [
878
],
"backdrop_path": "/5BwqwxMEjeFtdknRV792Svo0K1v.jpg",
"adult": false,
"overview": "An astronaut travels to the outer edges of the solar system to find his father and unravel a mystery that threatens the survival of Earth. In doing so, he uncovers secrets which challenge the nature of human existence and our place in the cosmos.",
"poster_path": "/xJUILftRf6TJxloOgrilOTJfeOn.jpg",
"popularity": 227.167,
"media_type": "movie"
},
]
}
Code should fetch the data and hold in it the array I created. So that I can use it to display in the front end.
I had to consume the exact same API for a similar project and this is how I did it.
When calling:
let response = try JSONDecoder().decode(MovieResponse.self, from: data)
It needs to match the same properties that the JSON response returns.
Below you'll see a MovieResponse struct and the Movie class, which will list all of the properties and return types that the JSON response returns.
The type adopts Codable so that it's decodable using a JSONDecoder instance.
See this official example for more information regarding Codable.
A type that can convert itself into and out of an external representation.
Provided they match then the JSONDecoder() will work to decode the data.
ContentView.swift:
struct ContentView: View {
#EnvironmentObject var movieViewModel: MovieListViewModel
var body: some View {
MovieList(movie: self.movieViewModel.movie)
}
}
MovieListViewModel.swift:
public class MovieListViewModel: ObservableObject {
public let objectWillChange = PassthroughSubject<MovieListViewModel, Never>()
private var movieResults: [Movie] = []
var movie: MovieResults = [Movie]() {
didSet {
objectWillChange.send(self)
}
}
func load(url: String = "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=<HIDDEN>") {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
guard let data = data else { return }
let response = try JSONDecoder().decode(MovieResponse.self, from: data)
DispatchQueue.main.async {
for movie in response.results {
self.movieResults.append(movie)
}
self.movie = self.movieResults
print("Finished loading Movies")
}
} catch {
print("Failed to decode: ", error)
}
}.resume()
}
}
MovieResponse.swift:
struct MovieResponse: Codable {
var page: Int
var total_results: Int
var total_pages: Int
var results: [Movie]
}
public class Movie: Codable, Identifiable {
public var popularity: Float
public var vote_count: Int
public var video: Bool
public var poster_path: String
public var id: Int
public var adult: Bool
public var backdrop_path: String
public var original_language: String
public var original_title: String
public var genre_ids: [Int]
public var title: String
public var vote_average: Float
public var overview: String
public var release_date: String
enum CodingKeys: String, CodingKey {
case popularity = "popularity"
case vote_count = "vote_count"
case video = "video"
case poster_path = "poster_path"
case id = "id"
case adult = "adult"
case backdrop_path = "backdrop_path"
case original_language = "original_language"
case original_title = "original_title"
case genre_ids = "genre_ids"
case title = "title"
case vote_average = "vote_average"
case overview = "overview"
case release_date = "release_date"
}
public init(popularity: Float, vote_count: Int, video: Bool, poster_path: String, id: Int, adult: Bool, backdrop_path: String, original_language: String, original_title: String, genre_ids: [Int], title: String, vote_average: Float, overview: String, release_date: String) {
self.popularity = popularity
self.vote_count = vote_count
self.video = video
self.poster_path = poster_path
self.id = id
self.adult = adult
self.backdrop_path = backdrop_path
self.original_language = original_language
self.original_title = original_title
self.genre_ids = genre_ids
self.title = title
self.vote_average = vote_average
self.overview = overview
self.release_date = release_date
}
public init() {
self.popularity = 0.0
self.vote_count = 0
self.video = false
self.poster_path = ""
self.id = 0
self.adult = false
self.backdrop_path = ""
self.original_language = ""
self.original_title = ""
self.genre_ids = []
self.title = ""
self.vote_average = 0.0
self.overview = ""
self.release_date = ""
}
}
public typealias MovieResults = [Movie]
MovieCellViewModel.swift:
public class MovieCellViewModel {
private var movie: Movie
public init(movie: Movie) {
self.movie = movie
}
public func getTitle() -> String {
return self.movie.title
}
// add more properties or functions here
}
MovieCell.swift:
struct MovieCell: View {
var movieCellViewModel: MovieCellViewModel
var body: some View {
Text(self.movieCellViewModel.getTitle())
}
}
MovieList.swift:
struct MovieList: View {
#EnvironmentObject var movieViewModel: MovieListViewModel
var movie: MovieResults
var body: some View {
List(self.movie) { movie in
MovieCell(movieCellViewModel: MovieCellViewModel(movie: movie))
}
}
}
I had a same error, but for the properties inside the struct for JSON object doesn't match JSON file. I fixed this error by setting the property names same as JSON file properties, and in the same order.
Swift file:
struct Coin: Decodable {
var asset_id_base: String
var rates: [CoinDetail]
}
struct CoinDetail: Decodable {
var time: String
var asset_id_quote: String
var rate: Double
}
JSON File:
{
"asset_id_base": "BTC",
"rates": [
{
"time": "2020-11-08T07:50:02.2865270Z",
"asset_id_quote": "CZK",
"rate": 328886.3419989546
},
{
"time": "2020-11-08T07:51:15.0750421Z",
"asset_id_quote": "GPL2",
"rate": 92123.4454168586
},
The Movie struct doesn't match the API response. You missed at least top level, like page and results array. Here you can paste your JSON answer and get the needed struct:
// MARK: - APIAnswer
struct APIAnswer: Codable {
let page: Int
let results: [Result]
}
// MARK: - Result
struct Result: Codable {
let id: Int
let video: Bool
let voteCount: Int
let voteAverage: Double
let title, releaseDate, originalLanguage, originalTitle: String
let genreIDS: [Int]
let backdropPath: String
let adult: Bool
let overview, posterPath: String
let popularity: Double
let mediaType: String
enum CodingKeys: String, CodingKey {
case id, video
case voteCount = "vote_count"
case voteAverage = "vote_average"
case title
case releaseDate = "release_date"
case originalLanguage = "original_language"
case originalTitle = "original_title"
case genreIDS = "genre_ids"
case backdropPath = "backdrop_path"
case adult, overview
case posterPath = "poster_path"
case popularity
case mediaType = "media_type"
}
}
Then you need to use the top level struct, like:
// ...
let decodeResult = try JSONDecoder().decode([APIAnswer].self, from: d)
let movieList = decodeResult.results
// the other advice: don't just take answer and put it to the array.
// API can have errors too, so you can get the array with 2 equal id, for example
update checked your code for second time: you use
let decodeResult = try JSONDecoder().decode([Movie].self, from: d)
instead of:
let decodeResult = try JSONDecoder().decode(MovieList.self, from: d)
P.S. better to attach also the full error from Xcode in future

Swift MVVM how can I implementing Array to model

Array inside the Rss Feed model and how can I solve this problem? I can't get data with MVVM from JSON.
Error: Cannot convert value of type '[Product]' to expected argument type 'Product'
I'm so sorry for bad English.
RSSFeed Model:
struct RSSFeed: Codable {
var title: String?
var description: String?
var icon: String?
var items: [Product]?
}
Product Model:
struct Product: Codable {
var id: String?
var title: String?
var specs: String?
var size: [String]?
var color: String?
var link: String?
var image: [String]?
var price: Price
}
Product View Model:
struct ProductViewModel {
private var product: Product!
init(product: Product) {
self.product = product
}
var id: String {
return product.id ?? ""
}
var title: String {
return product.title ?? ""
}
var specs: String {
return product.specs ?? ""
}
var size: [String] {
return product.size ?? [""]
}
var link: String {
return product.link ?? ""
}
var imageURL: [String] {
return product?.image ?? [""]
}
var price: String {
let rawValue = product.price.rawValue
let currency = product.price.currency
return "\(currency)\(rawValue)"
}
}
Product Provider (Problem is here)
class ProductProvider {
static let product = ProductProvider()
private init() {
}
func getProduct() -> [ProductViewModel] {
var rssFeed = RSSFeed()
let jsonFile = Bundle.main.path(forResource: "products", ofType: "json")
let data = try? Data(contentsOf: URL(fileURLWithPath: jsonFile!))
do {
rssFeed = try JSONDecoder().decode(RSSFeed.self, from: data!)
} catch let error {
print(error.localizedDescription)
}
if let product = (rssFeed.items) {
return ProductViewModel(product: product)
//Error: Cannot convert value of type '[Product]' to expected argument type 'Product'
}
return [ProductViewModel]()
}
}
How can I get JSON data ?
Change the if clause to
if let items = rssFeed.items {
return items.map {ProductViewModel(product: $0)}
}
Since one view model contains one product you need to convert the items array of Product to an array of ProductViewModel using the map function

My JSON decoder is not working and I'm not sure why

I'm trying to access a nested JSON variable called 'block' but I cannot seem to access it in any of the ways I've tried. Here's an example JSON message and my code:
{"account":"xrb_34tsctqcgctm8fhnpat351z4f64rgz8o9y7gwh1dutjf1r7iiwfzruawhatz","hash":"E5935C559748444D09E97D6D13FDB48B51F46A01FA9F6FB2DBD3576D684A53C6","block":"{\n \"type\": \"state\",\n \"account\": \"xrb_34tsctqcgctm8fhnpat351z4f64rgz8o9y7gwh1dutjf1r7iiwfzruawhatz\",\n \"previous\": \"78446816869EEEF4BC735B1A21AB33ED246A10303B87F0CAFD7CCD56406E0456\",\n \"representative\": \"xrb_3pczxuorp48td8645bs3m6c3xotxd3idskrenmi65rbrga5zmkemzhwkaznh\",\n \"balance\": \"320000000000000000000000000\",\n \"link\": \"8DE4EE799910E26C5E44CDD345B8C8070E1955284BC407660825B425FBEDBB6B\",\n \"link_as_account\": \"xrb_35h6xswsk694fjh6bmgmapwei3rg57ckiky61xm1ibfn6qxyugud9eo1fauk\",\n \"signature\": \"E4AF5BBDF583509DF3147004AB61FEC04F9007AC23A46A2E2E5BE4B65D0788F45F89EEC7B62D0F42144A9F5EA090EF3F58262070F07C59F1AD752B5CC3BF9D04\",\n \"work\": \"a56cb9e8d2539f73\"\n}\n","amount":"1`
struct IncomingBlock: Decodable {
var account: String
var hash: String
struct Block: Decodable {
var type: String
var previous: String
var link: String
var link_as_account: String
var representative: String
var account: String
var balance: String
var work: String
var signature: String
}
var block: Block
}
// in another file
guard let data = msg.data(using: .utf8) else { return }
guard let incomingBlock = try?JSONDecoder().decode(IncomingBlock.self, from: data) else { return }
Essentially to access the nested JSON variable block I had to decode the initial JSON message
do{
guard let data = inital_msg.data(using: .utf8) else { return }
let incomingBlock = try JSONDecoder().decode(IncomingBlock.self, from: data)
catch ...{}
and have the block's value cast to String in the model.
struct IncomingBlock: Decodable {
var account: String
var hash: String
var block: String
}
After that, I then decoded the initial messages block field once more like so
// Second JSON
let json = incomingBlock.block.data(using: .utf8)!
finally with the separated block model:
struct BlockMeta: Decodable {
var type: String
var previous: String
var link: String
var link_as_account: String
var representative: String
var account: String
var balance: String
var work: String
var signature: String
}
I could access the fields
let block = try JSONDecoder().decode(BlockMeta.self, from: json)
block.balance //returns "320000000000000000000000000"