Skip to content

Commit 6a3b5c8

Browse files
authored
Improve Insight entities parsing and introduce Codable (#763)
2 parents c2a51c2 + e57b288 commit 6a3b5c8

26 files changed

+1590
-418
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ _None._
3434

3535
### Breaking Changes
3636

37-
_None._
37+
- Changes the structure of `StatsAnnualAndMostPopularTimeInsight` to more accurately reflect JSON response. [#763]
38+
- Reworked the `NSDate` RFC3339 / WordPress.com JSON conversions API [#759]
3839

3940
### New Features
4041

@@ -46,7 +47,7 @@ _None._
4647

4748
### Internal Changes
4849

49-
_None._
50+
- Improved parsing using Codable for Stats Insight entities. [#763]
5051

5152
## 15.0.0
5253

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
public struct StatsAllAnnualInsight {
1+
public struct StatsAllAnnualInsight: Codable {
22
public let allAnnualInsights: [StatsAnnualInsight]
33

44
public init(allAnnualInsights: [StatsAnnualInsight]) {
55
self.allAnnualInsights = allAnnualInsights
66
}
7+
8+
private enum CodingKeys: String, CodingKey {
9+
case allAnnualInsights = "years"
10+
}
711
}
812

9-
public struct StatsAnnualInsight {
13+
public struct StatsAnnualInsight: Codable {
1014
public let year: Int
1115
public let totalPostsCount: Int
1216
public let totalWordsCount: Int
@@ -39,36 +43,42 @@ public struct StatsAnnualInsight {
3943
self.totalImagesCount = totalImagesCount
4044
self.averageImagesCount = averageImagesCount
4145
}
42-
}
4346

44-
extension StatsAllAnnualInsight: StatsInsightData {
45-
public static var pathComponent: String {
46-
return "stats/insights"
47+
private enum CodingKeys: String, CodingKey {
48+
case year
49+
case totalPostsCount = "total_posts"
50+
case totalWordsCount = "total_words"
51+
case averageWordsCount = "avg_words"
52+
case totalLikesCount = "total_likes"
53+
case averageLikesCount = "avg_likes"
54+
case totalCommentsCount = "total_comments"
55+
case averageCommentsCount = "avg_comments"
56+
case totalImagesCount = "total_images"
57+
case averageImagesCount = "avg_images"
4758
}
4859

49-
public init?(jsonDictionary: [String: AnyObject]) {
50-
guard let yearlyInsights = jsonDictionary["years"] as? [[String: AnyObject]] else {
51-
return nil
52-
}
53-
54-
let allAnnualInsights: [StatsAnnualInsight] = yearlyInsights.compactMap {
55-
guard let yearString = $0["year"] as? String,
56-
let year = Int(yearString) else {
57-
return nil
58-
}
60+
public init(from decoder: Decoder) throws {
61+
let container = try decoder.container(keyedBy: CodingKeys.self)
5962

60-
return StatsAnnualInsight(year: year,
61-
totalPostsCount: $0["total_posts"] as? Int ?? 0,
62-
totalWordsCount: $0["total_words"] as? Int ?? 0,
63-
averageWordsCount: $0["avg_words"] as? Double ?? 0,
64-
totalLikesCount: $0["total_likes"] as? Int ?? 0,
65-
averageLikesCount: $0["avg_likes"] as? Double ?? 0,
66-
totalCommentsCount: $0["total_comments"] as? Int ?? 0,
67-
averageCommentsCount: $0["avg_comments"] as? Double ?? 0,
68-
totalImagesCount: $0["total_images"] as? Int ?? 0,
69-
averageImagesCount: $0["avg_images"] as? Double ?? 0)
63+
if let year = Int(try container.decode(String.self, forKey: .year)) {
64+
self.year = year
65+
} else {
66+
throw DecodingError.dataCorruptedError(forKey: .year, in: container, debugDescription: "Year cannot be parsed into number.")
7067
}
68+
totalPostsCount = (try? container.decodeIfPresent(Int.self, forKey: .totalPostsCount)) ?? 0
69+
totalWordsCount = (try? container.decodeIfPresent(Int.self, forKey: .totalWordsCount)) ?? 0
70+
averageWordsCount = (try? container.decodeIfPresent(Double.self, forKey: .averageWordsCount)) ?? 0
71+
totalLikesCount = (try? container.decodeIfPresent(Int.self, forKey: .totalLikesCount)) ?? 0
72+
averageLikesCount = (try? container.decodeIfPresent(Double.self, forKey: .averageLikesCount)) ?? 0
73+
totalCommentsCount = (try? container.decodeIfPresent(Int.self, forKey: .totalCommentsCount)) ?? 0
74+
averageCommentsCount = (try? container.decodeIfPresent(Double.self, forKey: .averageCommentsCount)) ?? 0
75+
totalImagesCount = (try? container.decodeIfPresent(Int.self, forKey: .totalImagesCount)) ?? 0
76+
averageImagesCount = (try? container.decodeIfPresent(Double.self, forKey: .averageImagesCount)) ?? 0
77+
}
78+
}
7179

72-
self.allAnnualInsights = allAnnualInsights
80+
extension StatsAllAnnualInsight: StatsInsightData {
81+
public static var pathComponent: String {
82+
return "stats/insights"
7383
}
7484
}

Sources/WordPressKit/Models/Stats/Insights/StatsAllTimesInsight.swift

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
public struct StatsAllTimesInsight {
1+
public struct StatsAllTimesInsight: Codable {
22
public let postsCount: Int
33
public let viewsCount: Int
44
public let bestViewsDay: Date
@@ -16,23 +16,31 @@ public struct StatsAllTimesInsight {
1616
self.visitorsCount = visitorsCount
1717
self.bestViewsPerDayCount = bestViewsPerDayCount
1818
}
19+
20+
private enum CodingKeys: String, CodingKey {
21+
case postsCount = "posts"
22+
case viewsCount = "views"
23+
case bestViewsDay = "views_best_day"
24+
case visitorsCount = "visitors"
25+
case bestViewsPerDayCount = "views_best_day_total"
26+
}
27+
28+
private enum RootKeys: String, CodingKey {
29+
case stats
30+
}
1931
}
2032

2133
extension StatsAllTimesInsight: StatsInsightData {
34+
public init (from decoder: Decoder) throws {
35+
let rootContainer = try decoder.container(keyedBy: RootKeys.self)
36+
let container = try rootContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .stats)
2237

23-
// MARK: - StatsInsightData Conformance
24-
public init?(jsonDictionary: [String: AnyObject]) {
25-
guard
26-
let statsDict = jsonDictionary["stats"] as? [String: AnyObject],
27-
let bestViewsDayString = statsDict["views_best_day"] as? String
28-
else {
29-
return nil
30-
}
38+
self.postsCount = try container.decodeIfPresent(Int.self, forKey: .postsCount) ?? 0
39+
self.bestViewsPerDayCount = try container.decode(Int.self, forKey: .bestViewsPerDayCount)
40+
self.visitorsCount = try container.decodeIfPresent(Int.self, forKey: .visitorsCount) ?? 0
3141

32-
self.postsCount = statsDict["posts"] as? Int ?? 0
33-
self.bestViewsPerDayCount = statsDict["views_best_day_total"] as? Int ?? 0
34-
self.visitorsCount = statsDict["visitors"] as? Int ?? 0
35-
self.viewsCount = statsDict["views"] as? Int ?? 0
42+
self.viewsCount = try container.decodeIfPresent(Int.self, forKey: .viewsCount) ?? 0
43+
let bestViewsDayString = try container.decodeIfPresent(String.self, forKey: .bestViewsDay) ?? ""
3644
self.bestViewsDay = StatsAllTimesInsight.dateFormatter.date(from: bestViewsDayString) ?? Date()
3745
}
3846

Lines changed: 57 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,76 @@
1-
public struct StatsAnnualAndMostPopularTimeInsight {
2-
1+
public struct StatsAnnualAndMostPopularTimeInsight: Codable {
32
/// - A `DateComponents` object with one field populated: `weekday`.
43
public let mostPopularDayOfWeek: DateComponents
54
public let mostPopularDayOfWeekPercentage: Int
65

76
/// - A `DateComponents` object with one field populated: `hour`.
87
public let mostPopularHour: DateComponents
98
public let mostPopularHourPercentage: Int
9+
public let years: [Year]?
10+
11+
private enum CodingKeys: String, CodingKey {
12+
case mostPopularHour = "highest_hour"
13+
case mostPopularHourPercentage = "highest_hour_percent"
14+
case mostPopularDayOfWeek = "highest_day_of_week"
15+
case mostPopularDayOfWeekPercentage = "highest_day_percent"
16+
case years
17+
}
1018

11-
public let annualInsightsYear: Int
12-
13-
public let annualInsightsTotalPostsCount: Int
14-
public let annualInsightsTotalWordsCount: Int
15-
public let annualInsightsAverageWordsCount: Double
16-
17-
public let annualInsightsTotalLikesCount: Int
18-
public let annualInsightsAverageLikesCount: Double
19-
20-
public let annualInsightsTotalCommentsCount: Int
21-
public let annualInsightsAverageCommentsCount: Double
22-
23-
public let annualInsightsTotalImagesCount: Int
24-
public let annualInsightsAverageImagesCount: Double
25-
26-
public init(mostPopularDayOfWeek: DateComponents,
27-
mostPopularDayOfWeekPercentage: Int,
28-
mostPopularHour: DateComponents,
29-
mostPopularHourPercentage: Int,
30-
annualInsightsYear: Int,
31-
annualInsightsTotalPostsCount: Int,
32-
annualInsightsTotalWordsCount: Int,
33-
annualInsightsAverageWordsCount: Double,
34-
annualInsightsTotalLikesCount: Int,
35-
annualInsightsAverageLikesCount: Double,
36-
annualInsightsTotalCommentsCount: Int,
37-
annualInsightsAverageCommentsCount: Double,
38-
annualInsightsTotalImagesCount: Int,
39-
annualInsightsAverageImagesCount: Double) {
40-
self.mostPopularDayOfWeek = mostPopularDayOfWeek
41-
self.mostPopularDayOfWeekPercentage = mostPopularDayOfWeekPercentage
42-
43-
self.mostPopularHour = mostPopularHour
44-
self.mostPopularHourPercentage = mostPopularHourPercentage
45-
46-
self.annualInsightsYear = annualInsightsYear
47-
48-
self.annualInsightsTotalPostsCount = annualInsightsTotalPostsCount
49-
self.annualInsightsTotalWordsCount = annualInsightsTotalWordsCount
50-
self.annualInsightsAverageWordsCount = annualInsightsAverageWordsCount
51-
52-
self.annualInsightsTotalLikesCount = annualInsightsTotalLikesCount
53-
self.annualInsightsAverageLikesCount = annualInsightsAverageLikesCount
54-
55-
self.annualInsightsTotalCommentsCount = annualInsightsTotalCommentsCount
56-
self.annualInsightsAverageCommentsCount = annualInsightsAverageCommentsCount
19+
public struct Year: Codable {
20+
public let year: String
21+
public let totalPosts: Int
22+
public let totalWords: Int
23+
public let averageWords: Double
24+
public let totalLikes: Int
25+
public let averageLikes: Double
26+
public let totalComments: Int
27+
public let averageComments: Double
28+
public let totalImages: Int
29+
public let averageImages: Double
30+
31+
private enum CodingKeys: String, CodingKey {
32+
case year
33+
case totalPosts = "total_posts"
34+
case totalWords = "total_words"
35+
case averageWords = "avg_words"
36+
case totalLikes = "total_likes"
37+
case averageLikes = "avg_likes"
38+
case totalComments = "total_comments"
39+
case averageComments = "avg_comments"
40+
case totalImages = "total_images"
41+
case averageImages = "avg_images"
42+
}
5743

58-
self.annualInsightsTotalImagesCount = annualInsightsTotalImagesCount
59-
self.annualInsightsAverageImagesCount = annualInsightsAverageImagesCount
44+
public init(from decoder: Decoder) throws {
45+
let container = try decoder.container(keyedBy: CodingKeys.self)
46+
year = try container.decode(String.self, forKey: .year)
47+
totalPosts = (try? container.decodeIfPresent(Int.self, forKey: .totalPosts)) ?? 0
48+
totalWords = (try? container.decode(Int.self, forKey: .totalWords)) ?? 0
49+
averageWords = (try? container.decode(Double.self, forKey: .averageWords)) ?? 0
50+
totalLikes = (try? container.decode(Int.self, forKey: .totalLikes)) ?? 0
51+
averageLikes = (try? container.decode(Double.self, forKey: .averageLikes)) ?? 0
52+
totalComments = (try? container.decode(Int.self, forKey: .totalComments)) ?? 0
53+
averageComments = (try? container.decode(Double.self, forKey: .averageComments)) ?? 0
54+
totalImages = (try? container.decode(Int.self, forKey: .totalImages)) ?? 0
55+
averageImages = (try? container.decode(Double.self, forKey: .averageImages)) ?? 0
56+
}
6057
}
6158
}
6259

6360
extension StatsAnnualAndMostPopularTimeInsight: StatsInsightData {
6461
public static var pathComponent: String {
6562
return "stats/insights"
6663
}
64+
}
6765

68-
public init?(jsonDictionary: [String: AnyObject]) {
69-
guard
70-
let highestHour = jsonDictionary["highest_hour"] as? Int,
71-
let highestHourPercentageValue = jsonDictionary["highest_hour_percent"] as? Double,
72-
let highestDayOfWeek = jsonDictionary["highest_day_of_week"] as? Int,
73-
let highestDayOfWeekPercentageValue = jsonDictionary["highest_day_percent"] as? Double,
74-
let yearlyInsights = jsonDictionary["years"] as? [[String: AnyObject]],
75-
let latestYearlyInsight = yearlyInsights.last,
76-
let yearString = latestYearlyInsight["year"] as? String,
77-
let currentYear = Int(yearString)
78-
else {
79-
return nil
80-
}
66+
extension StatsAnnualAndMostPopularTimeInsight {
67+
public init(from decoder: Decoder) throws {
68+
let container = try decoder.container(keyedBy: CodingKeys.self)
69+
let years = try container.decodeIfPresent([Year].self, forKey: .years)
70+
let highestHour = try container.decode(Int.self, forKey: .mostPopularHour)
71+
let highestHourPercentageValue = try container.decode(Double.self, forKey: .mostPopularHourPercentage)
72+
let highestDayOfWeek = try container.decode(Int.self, forKey: .mostPopularDayOfWeek)
73+
let highestDayOfWeekPercentageValue = try container.decode(Double.self, forKey: .mostPopularDayOfWeekPercentage)
8174

8275
let mappedWeekday: ((Int) -> Int) = {
8376
// iOS Calendar system is `1-based` and uses Sunday as the first day of the week.
@@ -93,20 +86,6 @@ extension StatsAnnualAndMostPopularTimeInsight: StatsInsightData {
9386
self.mostPopularDayOfWeekPercentage = Int(highestDayOfWeekPercentageValue.rounded())
9487
self.mostPopularHour = hourComponents
9588
self.mostPopularHourPercentage = Int(highestHourPercentageValue.rounded())
96-
97-
self.annualInsightsYear = currentYear
98-
99-
self.annualInsightsTotalPostsCount = latestYearlyInsight["total_posts"] as? Int ?? 0
100-
self.annualInsightsTotalWordsCount = latestYearlyInsight["total_words"] as? Int ?? 0
101-
self.annualInsightsAverageWordsCount = latestYearlyInsight["avg_words"] as? Double ?? 0
102-
103-
self.annualInsightsTotalLikesCount = latestYearlyInsight["total_likes"] as? Int ?? 0
104-
self.annualInsightsAverageLikesCount = latestYearlyInsight["avg_likes"] as? Double ?? 0
105-
106-
self.annualInsightsTotalCommentsCount = latestYearlyInsight["total_comments"] as? Int ?? 0
107-
self.annualInsightsAverageCommentsCount = latestYearlyInsight["avg_comments"] as? Double ?? 0
108-
109-
self.annualInsightsTotalImagesCount = latestYearlyInsight["total_images"] as? Int ?? 0
110-
self.annualInsightsAverageImagesCount = latestYearlyInsight["avg_images"] as? Double ?? 0
89+
self.years = years
11190
}
11291
}

0 commit comments

Comments
 (0)