Skip to content

[SR-5381] Codable with array of base class #47955

@swift-ci

Description

@swift-ci
Previous ID SR-5381
Radar None
Original Reporter Okui (JIRA User)
Type Bug
Status Resolved
Resolution Duplicate

Attachment: Download

Environment

Xcode 9 beta 2

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: 037f67612a560e973f40d26c4e04b646

duplicates:

  • SR-5331 Swift 4 Decodable Loses Subclass Type Information

Issue Description:

I'm trying to migrate my app using NSCoder into Swift4's Codable. However, I'm struggling using Codable for classes.

Say, you have Shape class and 2 subclasses Rectangle and Oval. And Canvas contains multiple shapes as {{ var shapes: [Shape] }}.

class Shape {}
class Rectangle : Shape {
  var frame: CGRect
}
class Oval : Shape {
  var position: CGPoint
  var radius: CGFloat
}
struct Canvas {
   var shapes: [Shape]
}

Then implement Codable for all shapes. How do you encode/decode Canvas with these class types?

I can't encode class type into Shape itself because by the time Shape's required init(from decoder: Decoder) throws is called, it is already in Shape's initializer, so I can't initialize Rectangle nor Oval.

So, I ended up having wrapper struct that hold class type as string, and class reference to encode something like this:

struct ShapeEncoder : Codable {
  var shape: Shape

  init(shape: Shape) {
    self.shape = shape
  }

  private enum ShapeType : String, Codable {
    case rectangle
    case oval
  }

  func encode(to encoder: Encoder) throws {

    var container = encoder.unkeyedContainer()

    let shapeType: ShapeType
    if shape is Rectangle {
      shapeType = .rectangle
    }
    else {
      shapeType = .oval
    }

    try container.encode(shapeType)
    try container.encode(shape)
  }
  init(from decoder: Decoder) throws {
    // do similar thing
  }
}

struct Canvas : Codable {
  var shapes: [Shape]
  private enum CodingKeys: String, CodingKey {  case shapes  }

  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let encoders = try container.decode([ShapeEncoder].self, forKey: .shapes)
    self.shapes = encoders.map { $0.shape }
  }
  func encode(to encoder: Encoder) throws { ... }

[{"rectangle", "{"frame":[0, 0, 100, 100]}"}, {"oval":, "{position: [100, 100], radius: 50}"}]

(Playground file is attached.)

This would work, but I'm wondering... is there any better way to encode/decode with array base class type of array?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.compilerThe Swift compiler itself

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions