Skip to content

Commit

Permalink
Improve Insight entities parsing and introduce Codable (#763)
Browse files Browse the repository at this point in the history
  • Loading branch information
guarani authored Mar 30, 2024
2 parents c2a51c2 + e57b288 commit 6a3b5c8
Show file tree
Hide file tree
Showing 26 changed files with 1,590 additions and 418 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ _None._

### Breaking Changes

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

### New Features

Expand All @@ -46,7 +47,7 @@ _None._

### Internal Changes

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

## 15.0.0

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
public struct StatsAllAnnualInsight {
public struct StatsAllAnnualInsight: Codable {
public let allAnnualInsights: [StatsAnnualInsight]

public init(allAnnualInsights: [StatsAnnualInsight]) {
self.allAnnualInsights = allAnnualInsights
}

private enum CodingKeys: String, CodingKey {
case allAnnualInsights = "years"
}
}

public struct StatsAnnualInsight {
public struct StatsAnnualInsight: Codable {
public let year: Int
public let totalPostsCount: Int
public let totalWordsCount: Int
Expand Down Expand Up @@ -39,36 +43,42 @@ public struct StatsAnnualInsight {
self.totalImagesCount = totalImagesCount
self.averageImagesCount = averageImagesCount
}
}

extension StatsAllAnnualInsight: StatsInsightData {
public static var pathComponent: String {
return "stats/insights"
private enum CodingKeys: String, CodingKey {
case year
case totalPostsCount = "total_posts"
case totalWordsCount = "total_words"
case averageWordsCount = "avg_words"
case totalLikesCount = "total_likes"
case averageLikesCount = "avg_likes"
case totalCommentsCount = "total_comments"
case averageCommentsCount = "avg_comments"
case totalImagesCount = "total_images"
case averageImagesCount = "avg_images"
}

public init?(jsonDictionary: [String: AnyObject]) {
guard let yearlyInsights = jsonDictionary["years"] as? [[String: AnyObject]] else {
return nil
}

let allAnnualInsights: [StatsAnnualInsight] = yearlyInsights.compactMap {
guard let yearString = $0["year"] as? String,
let year = Int(yearString) else {
return nil
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

return StatsAnnualInsight(year: year,
totalPostsCount: $0["total_posts"] as? Int ?? 0,
totalWordsCount: $0["total_words"] as? Int ?? 0,
averageWordsCount: $0["avg_words"] as? Double ?? 0,
totalLikesCount: $0["total_likes"] as? Int ?? 0,
averageLikesCount: $0["avg_likes"] as? Double ?? 0,
totalCommentsCount: $0["total_comments"] as? Int ?? 0,
averageCommentsCount: $0["avg_comments"] as? Double ?? 0,
totalImagesCount: $0["total_images"] as? Int ?? 0,
averageImagesCount: $0["avg_images"] as? Double ?? 0)
if let year = Int(try container.decode(String.self, forKey: .year)) {
self.year = year
} else {
throw DecodingError.dataCorruptedError(forKey: .year, in: container, debugDescription: "Year cannot be parsed into number.")
}
totalPostsCount = (try? container.decodeIfPresent(Int.self, forKey: .totalPostsCount)) ?? 0
totalWordsCount = (try? container.decodeIfPresent(Int.self, forKey: .totalWordsCount)) ?? 0
averageWordsCount = (try? container.decodeIfPresent(Double.self, forKey: .averageWordsCount)) ?? 0
totalLikesCount = (try? container.decodeIfPresent(Int.self, forKey: .totalLikesCount)) ?? 0
averageLikesCount = (try? container.decodeIfPresent(Double.self, forKey: .averageLikesCount)) ?? 0
totalCommentsCount = (try? container.decodeIfPresent(Int.self, forKey: .totalCommentsCount)) ?? 0
averageCommentsCount = (try? container.decodeIfPresent(Double.self, forKey: .averageCommentsCount)) ?? 0
totalImagesCount = (try? container.decodeIfPresent(Int.self, forKey: .totalImagesCount)) ?? 0
averageImagesCount = (try? container.decodeIfPresent(Double.self, forKey: .averageImagesCount)) ?? 0
}
}

self.allAnnualInsights = allAnnualInsights
extension StatsAllAnnualInsight: StatsInsightData {
public static var pathComponent: String {
return "stats/insights"
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public struct StatsAllTimesInsight {
public struct StatsAllTimesInsight: Codable {
public let postsCount: Int
public let viewsCount: Int
public let bestViewsDay: Date
Expand All @@ -16,23 +16,31 @@ public struct StatsAllTimesInsight {
self.visitorsCount = visitorsCount
self.bestViewsPerDayCount = bestViewsPerDayCount
}

private enum CodingKeys: String, CodingKey {
case postsCount = "posts"
case viewsCount = "views"
case bestViewsDay = "views_best_day"
case visitorsCount = "visitors"
case bestViewsPerDayCount = "views_best_day_total"
}

private enum RootKeys: String, CodingKey {
case stats
}
}

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

// MARK: - StatsInsightData Conformance
public init?(jsonDictionary: [String: AnyObject]) {
guard
let statsDict = jsonDictionary["stats"] as? [String: AnyObject],
let bestViewsDayString = statsDict["views_best_day"] as? String
else {
return nil
}
self.postsCount = try container.decodeIfPresent(Int.self, forKey: .postsCount) ?? 0
self.bestViewsPerDayCount = try container.decode(Int.self, forKey: .bestViewsPerDayCount)
self.visitorsCount = try container.decodeIfPresent(Int.self, forKey: .visitorsCount) ?? 0

self.postsCount = statsDict["posts"] as? Int ?? 0
self.bestViewsPerDayCount = statsDict["views_best_day_total"] as? Int ?? 0
self.visitorsCount = statsDict["visitors"] as? Int ?? 0
self.viewsCount = statsDict["views"] as? Int ?? 0
self.viewsCount = try container.decodeIfPresent(Int.self, forKey: .viewsCount) ?? 0
let bestViewsDayString = try container.decodeIfPresent(String.self, forKey: .bestViewsDay) ?? ""
self.bestViewsDay = StatsAllTimesInsight.dateFormatter.date(from: bestViewsDayString) ?? Date()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,83 +1,76 @@
public struct StatsAnnualAndMostPopularTimeInsight {

public struct StatsAnnualAndMostPopularTimeInsight: Codable {
/// - A `DateComponents` object with one field populated: `weekday`.
public let mostPopularDayOfWeek: DateComponents
public let mostPopularDayOfWeekPercentage: Int

/// - A `DateComponents` object with one field populated: `hour`.
public let mostPopularHour: DateComponents
public let mostPopularHourPercentage: Int
public let years: [Year]?

private enum CodingKeys: String, CodingKey {
case mostPopularHour = "highest_hour"
case mostPopularHourPercentage = "highest_hour_percent"
case mostPopularDayOfWeek = "highest_day_of_week"
case mostPopularDayOfWeekPercentage = "highest_day_percent"
case years
}

public let annualInsightsYear: Int

public let annualInsightsTotalPostsCount: Int
public let annualInsightsTotalWordsCount: Int
public let annualInsightsAverageWordsCount: Double

public let annualInsightsTotalLikesCount: Int
public let annualInsightsAverageLikesCount: Double

public let annualInsightsTotalCommentsCount: Int
public let annualInsightsAverageCommentsCount: Double

public let annualInsightsTotalImagesCount: Int
public let annualInsightsAverageImagesCount: Double

public init(mostPopularDayOfWeek: DateComponents,
mostPopularDayOfWeekPercentage: Int,
mostPopularHour: DateComponents,
mostPopularHourPercentage: Int,
annualInsightsYear: Int,
annualInsightsTotalPostsCount: Int,
annualInsightsTotalWordsCount: Int,
annualInsightsAverageWordsCount: Double,
annualInsightsTotalLikesCount: Int,
annualInsightsAverageLikesCount: Double,
annualInsightsTotalCommentsCount: Int,
annualInsightsAverageCommentsCount: Double,
annualInsightsTotalImagesCount: Int,
annualInsightsAverageImagesCount: Double) {
self.mostPopularDayOfWeek = mostPopularDayOfWeek
self.mostPopularDayOfWeekPercentage = mostPopularDayOfWeekPercentage

self.mostPopularHour = mostPopularHour
self.mostPopularHourPercentage = mostPopularHourPercentage

self.annualInsightsYear = annualInsightsYear

self.annualInsightsTotalPostsCount = annualInsightsTotalPostsCount
self.annualInsightsTotalWordsCount = annualInsightsTotalWordsCount
self.annualInsightsAverageWordsCount = annualInsightsAverageWordsCount

self.annualInsightsTotalLikesCount = annualInsightsTotalLikesCount
self.annualInsightsAverageLikesCount = annualInsightsAverageLikesCount

self.annualInsightsTotalCommentsCount = annualInsightsTotalCommentsCount
self.annualInsightsAverageCommentsCount = annualInsightsAverageCommentsCount
public struct Year: Codable {
public let year: String
public let totalPosts: Int
public let totalWords: Int
public let averageWords: Double
public let totalLikes: Int
public let averageLikes: Double
public let totalComments: Int
public let averageComments: Double
public let totalImages: Int
public let averageImages: Double

private enum CodingKeys: String, CodingKey {
case year
case totalPosts = "total_posts"
case totalWords = "total_words"
case averageWords = "avg_words"
case totalLikes = "total_likes"
case averageLikes = "avg_likes"
case totalComments = "total_comments"
case averageComments = "avg_comments"
case totalImages = "total_images"
case averageImages = "avg_images"
}

self.annualInsightsTotalImagesCount = annualInsightsTotalImagesCount
self.annualInsightsAverageImagesCount = annualInsightsAverageImagesCount
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
year = try container.decode(String.self, forKey: .year)
totalPosts = (try? container.decodeIfPresent(Int.self, forKey: .totalPosts)) ?? 0
totalWords = (try? container.decode(Int.self, forKey: .totalWords)) ?? 0
averageWords = (try? container.decode(Double.self, forKey: .averageWords)) ?? 0
totalLikes = (try? container.decode(Int.self, forKey: .totalLikes)) ?? 0
averageLikes = (try? container.decode(Double.self, forKey: .averageLikes)) ?? 0
totalComments = (try? container.decode(Int.self, forKey: .totalComments)) ?? 0
averageComments = (try? container.decode(Double.self, forKey: .averageComments)) ?? 0
totalImages = (try? container.decode(Int.self, forKey: .totalImages)) ?? 0
averageImages = (try? container.decode(Double.self, forKey: .averageImages)) ?? 0
}
}
}

extension StatsAnnualAndMostPopularTimeInsight: StatsInsightData {
public static var pathComponent: String {
return "stats/insights"
}
}

public init?(jsonDictionary: [String: AnyObject]) {
guard
let highestHour = jsonDictionary["highest_hour"] as? Int,
let highestHourPercentageValue = jsonDictionary["highest_hour_percent"] as? Double,
let highestDayOfWeek = jsonDictionary["highest_day_of_week"] as? Int,
let highestDayOfWeekPercentageValue = jsonDictionary["highest_day_percent"] as? Double,
let yearlyInsights = jsonDictionary["years"] as? [[String: AnyObject]],
let latestYearlyInsight = yearlyInsights.last,
let yearString = latestYearlyInsight["year"] as? String,
let currentYear = Int(yearString)
else {
return nil
}
extension StatsAnnualAndMostPopularTimeInsight {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let years = try container.decodeIfPresent([Year].self, forKey: .years)
let highestHour = try container.decode(Int.self, forKey: .mostPopularHour)
let highestHourPercentageValue = try container.decode(Double.self, forKey: .mostPopularHourPercentage)
let highestDayOfWeek = try container.decode(Int.self, forKey: .mostPopularDayOfWeek)
let highestDayOfWeekPercentageValue = try container.decode(Double.self, forKey: .mostPopularDayOfWeekPercentage)

let mappedWeekday: ((Int) -> Int) = {
// iOS Calendar system is `1-based` and uses Sunday as the first day of the week.
Expand All @@ -93,20 +86,6 @@ extension StatsAnnualAndMostPopularTimeInsight: StatsInsightData {
self.mostPopularDayOfWeekPercentage = Int(highestDayOfWeekPercentageValue.rounded())
self.mostPopularHour = hourComponents
self.mostPopularHourPercentage = Int(highestHourPercentageValue.rounded())

self.annualInsightsYear = currentYear

self.annualInsightsTotalPostsCount = latestYearlyInsight["total_posts"] as? Int ?? 0
self.annualInsightsTotalWordsCount = latestYearlyInsight["total_words"] as? Int ?? 0
self.annualInsightsAverageWordsCount = latestYearlyInsight["avg_words"] as? Double ?? 0

self.annualInsightsTotalLikesCount = latestYearlyInsight["total_likes"] as? Int ?? 0
self.annualInsightsAverageLikesCount = latestYearlyInsight["avg_likes"] as? Double ?? 0

self.annualInsightsTotalCommentsCount = latestYearlyInsight["total_comments"] as? Int ?? 0
self.annualInsightsAverageCommentsCount = latestYearlyInsight["avg_comments"] as? Double ?? 0

self.annualInsightsTotalImagesCount = latestYearlyInsight["total_images"] as? Int ?? 0
self.annualInsightsAverageImagesCount = latestYearlyInsight["avg_images"] as? Double ?? 0
self.years = years
}
}
Loading

0 comments on commit 6a3b5c8

Please sign in to comment.