Uploaded image for project: 'Swift'
  1. Swift
  2. SR-5381

Codable with array of base class

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Resolved
    • Priority: Medium
    • Resolution: Duplicate
    • Component/s: Compiler
    • Labels:
      None
    • Environment:

      Xcode 9 beta 2

      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?

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              Unassigned Unassigned
              Reporter:
              Okui Kaz Okui
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: