Как я могу создать подклассы, используя Argo и Swift?

Я использую Argo в приложении Swift для декодирования JSON в объекты. У меня есть JSON, как это:

"activities": [
  {
    "id": "intro-to-the-program",
    "type": "session",
    "audio": "intro-to-the-program.mp3"
  },
  {
    "id": "goal-setting",
    "type": "session",
    "audio": "goal-setting.mp3"
  },
  {
    "id": "onboarding-quiz",
    "type": "quiz"
  }
]

Основываясь на типе, я на самом деле хочу создать экземпляр подкласса класса Activity (ActivitySession, ActivityQuiz и т. Д.), Чтобы подкласс выполнял свое собственное декодирование.

Как я могу это сделать? Функция декодирования верхнего уровня ожидает тип возвращаемого значения Decoded<Activity>и ни один из моих подходов пока не способен победить его.

1 ответ

Решение

Вот один из способов сделать это, включив тип, чтобы условно декодировать его и получить хорошее сообщение об ошибке, когда указан недопустимый тип.

struct ThingWithActivities: Decodable {
  let activities: [Activity]

  static func decode(json: JSON) -> Decoded<ThingWithActivities> {
    return curry(ThingWithActivities.init)
      <^> json <|| "activities"
  }
}

class Activity: Decodable {
  let id: String

  init(id: String) {
    self.id = id
  }

  class func decode(json: JSON) -> Decoded<Activity> {
    let decodedType: Decoded<String> = json <| "type"
    return decodedType.flatMap { type in
      switch type {
        case "session": return ActivitySession.decode(json)
        case "quiz": return ActivityQuiz.decode(json)
      default:
        return .Failure(.Custom("Expected valid type, found: \(type)"))
      }
    }
  }
}

class ActivitySession: Activity {
  let audio: String

  init(id: String, audio: String) {
    self.audio = audio
    super.init(id: id)
  }

  override static func decode(json: JSON) -> Decoded<Activity> {
    return curry(ActivitySession.init)
      <^> json <| "id"
      <*> json <| "audio"
  }

}

class ActivityQuiz: Activity {
  override static func decode(json: JSON) -> Decoded<Activity> {
    return curry(ActivityQuiz.init)
      <^> json <| "id"
  }
}

let activities: Decoded<ThingWithActivities>? = JSONFromFile("activities").flatMap(decode)
print(activities) 
// => Optional(Success(ThingWithActivities(activities: [ActivitySession, ActivitySession, ActivityQuiz])))

Ключевая часть вытаскивает тип и .flatMapнад ним, чтобы условно декодировать в тип, которым он должен быть.

Другие вопросы по тегам