Swift 4.2中如何将继承类转换为JSON格式?
Hey there! Let's work through this problem together. The main issue you're hitting is that Swift's JSONEncoder doesn't automatically handle subclass properties by default—when you store a subclass instance in an array of the base class type, it only encodes the properties defined in the base class. Let's fix this and generate your desired JSON output.
First, let's recap your target JSON for reference:
{ "title": "newSurvey", "pages": [ { "questions": [ { "title": "Untitled Question", "type": "QUESTION_SLIDER", "content": { "min": "1", "max": "10", "step": "3", "defaultValue": "5" } }, { "title": "asdasddfdf", "type": "QUESTION_TEXT", "choices": ["choice1","choice2"] } ] } ] }
Solution 1: Use Enums (Recommended for Swift)
Swift enums with associated values are a cleaner, safer way to handle polymorphic data like different question types, compared to class inheritance. Here's how to implement it:
import Foundation // Define question types to avoid hardcoded strings enum QuestionType: String, Encodable { case slider = "QUESTION_SLIDER" case text = "QUESTION_TEXT" } // Slider-specific content structure struct SliderContent: Encodable { let min: String let max: String let step: String let defaultValue: String } // Enum representing all possible question types enum Question: Encodable { case slider(title: String, content: SliderContent) case text(title: String, choices: [String]) // Custom encoding to match your JSON structure func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .slider(let title, let content): try container.encode(title, forKey: .title) try container.encode(QuestionType.slider.rawValue, forKey: .type) try container.encode(content, forKey: .content) case .text(let title, let choices): try container.encode(title, forKey: .title) try container.encode(QuestionType.text.rawValue, forKey: .type) try container.encode(choices, forKey: .choices) } } // Coding keys for JSON properties private enum CodingKeys: String, CodingKey { case title, type, content, choices } } // Page structure containing questions struct Page: Encodable { let questions: [Question] } // Top-level survey structure struct Survey: Encodable { let title: String let pages: [Page] // Convert survey to pretty-printed JSON string func toJSONString() -> String? { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted do { let data = try encoder.encode(self) return String(data: data, encoding: .utf8) } catch { print("Encoding error: \(error)") return nil } } }
Usage Example
// Create slider question let sliderContent = SliderContent(min: "1", max: "10", step: "3", defaultValue: "5") let sliderQuestion = Question.slider(title: "Untitled Question", content: sliderContent) // Create text question let textQuestion = Question.text(title: "asdasddfdf", choices: ["choice1", "choice2"]) // Build survey and generate JSON let page = Page(questions: [sliderQuestion, textQuestion]) let survey = Survey(title: "newSurvey", pages: [page]) if let jsonString = survey.toJSONString() { print(jsonString) }
Solution 2: Use Class Inheritance (If You Prefer)
If you want to stick with class-based inheritance, you need to override the encode(to:) method in both the base class and subclasses to ensure all properties are encoded:
import Foundation enum QuestionType: String, Encodable { case slider = "QUESTION_SLIDER" case text = "QUESTION_TEXT" } // Base Question class with custom encoding class Question: Encodable { var title: String let type: QuestionType init(title: String, type: QuestionType) { self.title = title self.type = type } // Required for Decodable (we're skipping decoding here) required init(from decoder: Decoder) throws { fatalError("init(from:) has not been implemented") } // Encode base class properties func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(title, forKey: .title) try container.encode(type.rawValue, forKey: .type) } enum CodingKeys: String, CodingKey { case title, type, content, choices } } // Slider Question subclass class SliderQuestion: Question { var content: SliderContent init(title: String, content: SliderContent) { self.content = content super.init(title: title, type: .slider) } required init(from decoder: Decoder) throws { fatalError("init(from:) has not been implemented") } // Override to encode subclass properties override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) // Encode base properties first var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(content, forKey: .content) } } struct SliderContent: Encodable { let min: String let max: String let step: String let defaultValue: String } // Text Question subclass class TextQuestion: Question { var choices: [String] init(title: String, choices: [String]) { self.choices = choices super.init(title: title, type: .text) } required init(from decoder: Decoder) throws { fatalError("init(from:) has not been implemented") } // Override to encode subclass properties override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) // Encode base properties first var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(choices, forKey: .choices) } } // Page and Survey structures struct Page: Encodable { let questions: [Question] } class Survey: Encodable { let title: String let pages: [Page] init(title: String, pages: [Page]) { self.title = title self.pages = pages } func toJSONString() -> String? { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted do { let data = try encoder.encode(self) return String(data: data, encoding: .utf8) } catch { print("Encoding error: \(error)") return nil } } }
Usage Example
let sliderContent = SliderContent(min: "1", max: "10", step: "3", defaultValue: "5") let sliderQuestion = SliderQuestion(title: "Untitled Question", content: sliderContent) let textQuestion = TextQuestion(title: "asdasddfdf", choices: ["choice1", "choice2"]) let page = Page(questions: [sliderQuestion, textQuestion]) let survey = Survey(title: "newSurvey", pages: [page]) if let jsonString = survey.toJSONString() { print(jsonString) }
Why Your Original Code Didn't Work
When you stored SliderQuestion instances in a [Question] array, JSONEncoder only recognized the base Question type, so it only encoded the title and description properties from the base class—ignoring the content and TYPE properties from the subclass. The solutions above explicitly handle encoding all properties for each question type.
内容的提问来源于stack exchange,提问作者Ammar Alhelali

