Skip to content

Instantly share code, notes, and snippets.

@KentarouKanno
Last active October 28, 2018 08:44
Show Gist options
  • Save KentarouKanno/de63a8fd05f20cc8f81c5a8059693623 to your computer and use it in GitHub Desktop.
Save KentarouKanno/de63a8fd05f20cc8f81c5a8059693623 to your computer and use it in GitHub Desktop.
  • URL
    https://itunes.apple.com/lookup?id=XXXXXXXX&country=JP
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let url = URL(string: "https://itunes.apple.com/lookup?id=1242266654&country=JP")!

session.dataTask(with: url) { (data: Data?, response: URLResponse?, error: Error?) in
    if let data = data {
        let str = String(data: data, encoding: .utf8)!
        print(str)
        let obj = try? JSONDecoder().decode(AppDataResponse.self, from: data)
    }
}.resume()
import Foundation
import XCTest

// ---------- Data -------------

guard let fileURL = Bundle.main.url(forResource: "response", withExtension: "json"),
    let content = try? String(contentsOf: fileURL, encoding: String.Encoding.utf8),
    let data = content.data(using: .utf8) else {
        fatalError("JSON取得失敗!")
}

// ---------- extension -------------

extension KeyedDecodingContainer {
    func decode<ResultType: Decodable>(forKey key: Key, defaultValue: ResultType) -> ResultType {
        return (try? decode(ResultType.self, forKey: key)) ?? defaultValue
    }
    
    func convertStringDecode<ResultType: ConvertResultType>(forKey key: Key) -> ResultType {
        let num = try? decode(Int.self, forKey: key)
        return ResultType.toResultType(num: num)
    }
}

typealias ConvertResultType = ConvertStringType & Decodable

internal protocol ConvertStringType {
    static func toResultType(num: Int?) -> Self
}

extension String: ConvertStringType {
    internal static func toResultType(num: Int?) -> String {
        guard let num = num else { return "" }
        return String(num)
    }
}

// ---------- Data Class -------------


struct AppDataResponse: Codable {
    
    let resultCount: Int
    let results: [AppDataResults]
    
    enum CodingKeys: String, CodingKey {
        case resultCount
        case results
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        resultCount = container.decode(forKey: .resultCount, defaultValue: 0)
        results = container.decode(forKey: .results, defaultValue: [])
    }
}

struct AppDataResults: Codable {
    
    let screenshotUrls: [String]
    let ipadScreenshotUrls: [String]
    let appletvScreenshotUrls: [String]
    let artworkUrl512: String
    let artistViewUrl: String
    let artworkUrl60: String
    let artworkUrl100: String
    let isGameCenterEnabled: Bool
    let advisories: [String]
    let kind: String
    let features: [String]
    let supportedDevices: [String]
    let averageUserRatingForCurrentVersion: Float
    let languageCodesISO2A: [String]
    let fileSizeBytes: [String]
    let sellerUrl: String
    let userRatingCountForCurrentVersion: Int
    let trackContentRating: String
    let trackCensoredName: String
    let trackViewUrl: String
    let contentAdvisoryRating: String
    let releaseNotes: String
    let minimumOsVersion: String
    let currentVersionReleaseDate: Date?
    let genreIds: [String]
    let primaryGenreName: String
    let releaseDate: Date?
    let wrapperType: String
    let version: String
    let currency: String
    let description: String
    let artistId: String
    let artistName: String
    let genres: [String]
    let price: Float
    let bundleId: String
    let formattedPrice: String
    let trackId: String
    let trackName: String
    let isVppDeviceBasedLicensingEnabled: Bool
    let sellerName: String
    let primaryGenreId: Int
    let averageUserRating: Float
    let userRatingCount: Int
    
    enum CodingKeys: String, CodingKey {
        case screenshotUrls
        case ipadScreenshotUrls
        case appletvScreenshotUrls
        case artworkUrl512
        case artistViewUrl
        case artworkUrl60
        case artworkUrl100
        case isGameCenterEnabled
        case advisories
        case kind
        case features
        case supportedDevices
        case averageUserRatingForCurrentVersion
        case languageCodesISO2A
        case fileSizeBytes
        case sellerUrl
        case userRatingCountForCurrentVersion
        case trackContentRating
        case trackCensoredName
        case trackViewUrl
        case contentAdvisoryRating
        case releaseNotes
        case minimumOsVersion
        case currentVersionReleaseDate
        case genreIds
        case primaryGenreName
        case releaseDate
        case wrapperType
        case version
        case currency
        case description
        case artistId
        case artistName
        case genres
        case price
        case bundleId
        case formattedPrice
        case trackId
        case trackName
        case isVppDeviceBasedLicensingEnabled
        case sellerName
        case primaryGenreId
        case averageUserRating
        case userRatingCount
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        screenshotUrls = container.decode(forKey: .screenshotUrls, defaultValue: [])
        ipadScreenshotUrls = container.decode(forKey: .ipadScreenshotUrls, defaultValue: [])
        appletvScreenshotUrls = container.decode(forKey: .appletvScreenshotUrls, defaultValue: [])
        artworkUrl512 = container.decode(forKey: .artworkUrl512, defaultValue: "")
        artistViewUrl = container.decode(forKey: .artistViewUrl, defaultValue: "")
        artworkUrl60 = container.decode(forKey: .artworkUrl60, defaultValue: "")
        artworkUrl100 = container.decode(forKey: .artworkUrl100, defaultValue: "")
        isGameCenterEnabled = container.decode(forKey: .isGameCenterEnabled, defaultValue: false)
        advisories = container.decode(forKey: .advisories, defaultValue: [])
        kind = container.decode(forKey: .kind, defaultValue: "")
        features = container.decode(forKey: .features, defaultValue: [])
        supportedDevices = container.decode(forKey: .supportedDevices, defaultValue: [])
        averageUserRatingForCurrentVersion = container.decode(forKey: .averageUserRatingForCurrentVersion, defaultValue: 0.0)
        languageCodesISO2A = container.decode(forKey: .languageCodesISO2A, defaultValue: [])
        
        fileSizeBytes = container.decode(forKey: .fileSizeBytes, defaultValue: [])
        sellerUrl = container.decode(forKey: .sellerUrl, defaultValue: "")
        userRatingCountForCurrentVersion = container.decode(forKey: .userRatingCountForCurrentVersion, defaultValue: 0)
        trackContentRating = container.decode(forKey: .trackContentRating, defaultValue: "")
        trackCensoredName = container.decode(forKey: .trackCensoredName, defaultValue: "")
        trackViewUrl = container.decode(forKey: .trackViewUrl, defaultValue: "")
        contentAdvisoryRating = container.decode(forKey: .contentAdvisoryRating, defaultValue: "")
        releaseNotes = container.decode(forKey: .releaseNotes, defaultValue: "")
        minimumOsVersion = container.decode(forKey: .minimumOsVersion, defaultValue: "")
        currentVersionReleaseDate = container.decode(forKey: .currentVersionReleaseDate, defaultValue: Date())
        genreIds = container.decode(forKey: .genreIds, defaultValue: [])
        primaryGenreName = container.decode(forKey: .primaryGenreName, defaultValue: "")
        releaseDate = container.decode(forKey: .releaseDate, defaultValue: Date())

        wrapperType = container.decode(forKey: .wrapperType, defaultValue: "")
        version = container.decode(forKey: .version, defaultValue: "")
        currency = container.decode(forKey: .currency, defaultValue: "")
        description = container.decode(forKey: .description, defaultValue: "")
        artistId = container.decode(forKey: .artistId, defaultValue: "")
        artistName = container.decode(forKey: .artistName, defaultValue: "")
        genres = container.decode(forKey: .genres, defaultValue: [])
        price = container.decode(forKey: .price, defaultValue: 0.0)
        bundleId = container.decode(forKey: .bundleId, defaultValue: "")
        formattedPrice = container.decode(forKey: .formattedPrice, defaultValue: "")


        trackId = container.decode(forKey: .trackId, defaultValue: "")
        trackName = container.decode(forKey: .trackName, defaultValue: "")
        isVppDeviceBasedLicensingEnabled = container.decode(forKey: .isVppDeviceBasedLicensingEnabled, defaultValue: false)
        sellerName = container.decode(forKey: .sellerName, defaultValue: "")
        primaryGenreId = container.decode(forKey: .primaryGenreId, defaultValue: 0)
        averageUserRating = container.decode(forKey: .averageUserRating, defaultValue: 0.0)
        userRatingCount = container.decode(forKey: .userRatingCount, defaultValue: 0)
    }
}



// ---------- Decode -------------

let obj = try? JSONDecoder().decode(AppDataResponse.self, from: data)

if let obj = obj {
    
    print(obj.resultCount)
    
    print(obj.results.first!.screenshotUrls.forEach { print($0) })
    print(obj.results.first!.releaseDate)
}
  • JSON例
{
    "resultCount": 1,
    "results": [
        {
            "screenshotUrls": [
                "https://is2-ssl.mzstatic.com/image/thumb/Purple115/v4/c7/68/41/c768410d-e23c-3f3d-5b65-eaa3a7ea05e6/source/392x696bb.jpg",
                "https://is1-ssl.mzstatic.com/image/thumb/Purple115/v4/00/15/b4/0015b4d7-caac-ab05-b12a-f49daae1a55c/source/392x696bb.jpg",
                "https://is3-ssl.mzstatic.com/image/thumb/Purple125/v4/0d/5f/76/0d5f767e-e70f-677d-8139-277021725478/source/392x696bb.jpg",
                "https://is2-ssl.mzstatic.com/image/thumb/Purple125/v4/82/c4/d2/82c4d2d4-415f-2a38-2e35-7ab3f9aad4bd/source/392x696bb.jpg",
                "https://is5-ssl.mzstatic.com/image/thumb/Purple115/v4/94/89/20/94892017-ae88-a453-41e5-47afe02ebe65/source/392x696bb.jpg"
            ],
            "ipadScreenshotUrls": [],
            "appletvScreenshotUrls": [],
            "artworkUrl512": "https://is2-ssl.mzstatic.com/image/thumb/Purple118/v4/68/29/b6/6829b688-6a7f-de43-d76a-277fbc2db529/source/512x512bb.jpg",
            "artistViewUrl": "https://itunes.apple.com/jp/developer/japan-professional-football-league/id1242266653?uo=4",
            "artworkUrl60": "https://is2-ssl.mzstatic.com/image/thumb/Purple118/v4/68/29/b6/6829b688-6a7f-de43-d76a-277fbc2db529/source/60x60bb.jpg",
            "artworkUrl100": "https://is2-ssl.mzstatic.com/image/thumb/Purple118/v4/68/29/b6/6829b688-6a7f-de43-d76a-277fbc2db529/source/100x100bb.jpg",
            "isGameCenterEnabled": false,
            "advisories": [
                "ギャンブルまたはコンテスト"
            ],
            "kind": "software",
            "features": [],
            "supportedDevices": [
                "iPad2Wifi-iPad2Wifi",
                "iPad23G-iPad23G",
                "iPhone4S-iPhone4S",
                "iPadThirdGen-iPadThirdGen",
                "iPadThirdGen4G-iPadThirdGen4G",
                "iPhone5-iPhone5",
                "iPodTouchFifthGen-iPodTouchFifthGen",
                "iPadFourthGen-iPadFourthGen",
                "iPadFourthGen4G-iPadFourthGen4G",
                "iPadMini-iPadMini",
                "iPadMini4G-iPadMini4G",
                "iPhone5c-iPhone5c",
                "iPhone5s-iPhone5s",
                "iPadAir-iPadAir",
                "iPadAirCellular-iPadAirCellular",
                "iPadMiniRetina-iPadMiniRetina",
                "iPadMiniRetinaCellular-iPadMiniRetinaCellular",
                "iPhone6-iPhone6",
                "iPhone6Plus-iPhone6Plus",
                "iPadAir2-iPadAir2",
                "iPadAir2Cellular-iPadAir2Cellular",
                "iPadMini3-iPadMini3",
                "iPadMini3Cellular-iPadMini3Cellular",
                "iPodTouchSixthGen-iPodTouchSixthGen",
                "iPhone6s-iPhone6s",
                "iPhone6sPlus-iPhone6sPlus",
                "iPadMini4-iPadMini4",
                "iPadMini4Cellular-iPadMini4Cellular",
                "iPadPro-iPadPro",
                "iPadProCellular-iPadProCellular",
                "iPadPro97-iPadPro97",
                "iPadPro97Cellular-iPadPro97Cellular",
                "iPhoneSE-iPhoneSE",
                "iPhone7-iPhone7",
                "iPhone7Plus-iPhone7Plus",
                "iPad611-iPad611",
                "iPad612-iPad612",
                "iPad71-iPad71",
                "iPad72-iPad72",
                "iPad73-iPad73",
                "iPad74-iPad74",
                "iPhone8-iPhone8",
                "iPhone8Plus-iPhone8Plus",
                "iPhoneX-iPhoneX",
                "iPad75-iPad75",
                "iPad76-iPad76",
                "iPhoneXS-iPhoneXS",
                "iPhoneXSMax-iPhoneXSMax",
                "iPhoneXR-iPhoneXR"
            ],
            "averageUserRatingForCurrentVersion": 4.5,
            "languageCodesISO2A": [
                "JA"
            ],
            "fileSizeBytes": "98247680",
            "sellerUrl": "https://www.jleague.jp/app/",
            "userRatingCountForCurrentVersion": 535,
            "trackContentRating": "17+",
            "trackCensoredName": "Club J.LEAGUE",
            "trackViewUrl": "https://itunes.apple.com/jp/app/club-j-league/id1242266654?mt=8&uo=4",
            "contentAdvisoryRating": "17+",
            "releaseNotes": "2018Jリーグアウォーズ応募に関する機能を追加しました。\nその他、軽微な修正を行っています。",
            "minimumOsVersion": "9.3.5",
            "currentVersionReleaseDate": "2018-10-18T04:25:04Z",
            "genreIds": [
                "6004"
            ],
            "primaryGenreName": "Sports",
            "releaseDate": "2017-07-31T15:44:36Z",
            "wrapperType": "software",
            "version": "1.2.13",
            "currency": "JPY",
            "description": "◆◆Jリーグ公式アプリ「Club J.LEAGUE」◆◆\n・お気に入りクラブを登録してニュースや試合速報、チケットの情報をまとめてチェック!\n・メダルを集めて、新しい観戦仲間が増やせる「明治安田生命Jリーグチャレンジ」に参加して日本のサッカーを盛り上げよう!\n・試合日にスタジアムでチェックインすれば、観戦メダルをGETできます!\n\n①お気に入りクラブを登録\n好きなクラブをお気に入りに登録しよう!\nそのクラブのニュースや試合の日程・速報をまとめてチェックできます。Push通知で試合開始やゴールの通知を受け取ることができます。\n\n②チケット購入\nお気に入りクラブの試合を一括管理!\nアプリから直接チケットを購入してスタジアムで応援しよう!\n\n③明治安田生命 Jリーグチャレンジ\n様々なミッションをクリアするとメダルがもらえます。\nメダルを3枚集めてJリーグチャレンジに挑戦すると、\n友達を誘えるペアチケットが当たるチャンスが!\n\n④観戦メダルをゲット\n試合日にスタジアムでチェックインするとメダルがもらえます!\nJチャレに挑戦するには観戦メダルが必須です。\n応援に来たときは必ずアプリをチェックしてください!\n\n⑤限定キャンペーン\nメダルをさらに集めるとランクがあがっていきます!\n上位のランクの方だけが参加できるキャンペーンもありますので\nどんどんメダルを集めてお得なキャンペーンに参加しましょう!\n\n--------------------------------------------------------\n■ご利用にあたっての注意事項\n--------------------------------------------------------\n・「Club J.LEAGUE」のご利用に際しては通信環境が必要となります。お客様に通信環境を整えていただく必要がございますので、あらかじめご了承ください。\n※お客様と通信会社とのご契約内容によって通信費が発生する場合がございますのでご注意ください。\n・通信環境が不安定だと必要な情報を読み取る事ができない場合がございます。圏外になりやすい場所や、WiFiなどでも通信環境が不安定ですとアプリが正常に稼働しない場合がございますのでご注意ください。\n・お客様のお持ちの機種の機能、OSなどのソフトウェアの環境によって、正常に稼働しない場合がございます。あらかじめご了承ください。\n・チェックイン機能をご利用の際は、アプリのGPS機能をONにする必要があります。通信環境によっては正確に位置情報を読み取れない場合がございますのでご注意ください。\n※WiFiをONにすることによってGPSの精度が高まります。\n\n--------------------------------------------------------\n著作権について\n--------------------------------------------------------\n本アプリに記載されている内容の著作権は、Jリーグおよび情報提供者に帰属し、いかなる目的であれ無断で複製、引用、転送、頒布、上演、改編、修正、追加など一切の行為を禁止いたします。",
            "artistId": "1242266653",
            "artistName": "Japan Professional Football League",
            "genres": [
                "スポーツ"
            ],
            "price": 0.00,
            "bundleId": "jp.jleague.club",
            "formattedPrice": "無料",
            "trackId": "1242266654",
            "trackName": "Club J.LEAGUE",
            "isVppDeviceBasedLicensingEnabled": true,
            "sellerName": "JAPAN PROFESSIONAL FOOTBALL LEAGUE",
            "primaryGenreId": 6004,
            "averageUserRating": 4.5,
            "userRatingCount": 17590
        }
    ]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment