mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-12-11 10:12:45 +00:00
Support object schemas with only additionalProperties. (#6492)
Previously, we had implemented the Codable protocol by simply claiming conformance, and making sure that each of our internal classes also implemented the Codable protocol. So our model classes looked like:
class MyModel: Codable {
var propInt: Int
var propString: String
}
class MyOtherModel: Codable {
var propModel: MyModel
}
Previously, our additionalProperties implementation would have meant an object schema with an additionalProperties of Int type would have looked like:
class MyModelWithAdditionalProperties: Codable {
var additionalProperties: [String: Int]
}
But the default implementation of Codable would have serialized MyModelWithAdditionalProperties like this:
{
"additionalProperties": {
"myInt1": 1,
"myInt2": 2,
"myInt3": 3
}
}
The default implementation would put the additionalProperties in its own dictionary (which would be incorrect), as opposed to the desired serialization of:
{
"myInt1": 1,
"myInt2": 2,
"myInt3": 3
}
So therefore, the only way to support this was to do our own implementation of the Codable protocol. The Codable protocol is actually two protocols: Encodable and Decodable.
So therefore, this change generates implementations of Encodable and Decodable for each generated model class. So the new generated classes look like:
class MyModel: Codable {
var propInt: Int
var propString: String
// Encodable protocol methods
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: String.self)
try container.encode(propInt, forKey: "propInt")
try container.encode(propString, forKey: "propString")
}
// Decodable protocol methods
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: String.self)
propInt = try container.decode(Int.self, forKey: "propInt")
propString = try container.decode(String.self, forKey: "propString")
}
}
class MyOtherModel: Codable {
var propModel: MyModel
// Encodable protocol methods
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: String.self)
try container.encode(propModel, forKey: "propModel")
}
// Decodable protocol methods
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: String.self)
propModel = try container.decode(MyModel.self, forKey: "propModel")
}
}
This commit is contained in:
@@ -85,6 +85,92 @@ extension UUID: JSONEncodable {
|
||||
}
|
||||
}
|
||||
|
||||
extension String: CodingKey {
|
||||
|
||||
public var stringValue: String {
|
||||
return self
|
||||
}
|
||||
|
||||
public init?(stringValue: String) {
|
||||
self.init(stringLiteral: stringValue)
|
||||
}
|
||||
|
||||
public var intValue: Int? {
|
||||
return nil
|
||||
}
|
||||
|
||||
public init?(intValue: Int) {
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension KeyedEncodingContainerProtocol {
|
||||
|
||||
public mutating func encodeArray<T>(_ values: [T], forKey key: Self.Key) throws where T : Encodable {
|
||||
var arrayContainer = nestedUnkeyedContainer(forKey: key)
|
||||
try arrayContainer.encode(contentsOf: values)
|
||||
}
|
||||
|
||||
public mutating func encodeArrayIfPresent<T>(_ values: [T]?, forKey key: Self.Key) throws where T : Encodable {
|
||||
if let values = values {
|
||||
try encodeArray(values, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func encodeMap<T>(_ pairs: [Self.Key: T]) throws where T : Encodable {
|
||||
for (key, value) in pairs {
|
||||
try encode(value, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func encodeMapIfPresent<T>(_ pairs: [Self.Key: T]?) throws where T : Encodable {
|
||||
if let pairs = pairs {
|
||||
try encodeMap(pairs)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension KeyedDecodingContainerProtocol {
|
||||
|
||||
public func decodeArray<T>(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T : Decodable {
|
||||
var tmpArray = [T]()
|
||||
|
||||
var nestedContainer = try nestedUnkeyedContainer(forKey: key)
|
||||
while !nestedContainer.isAtEnd {
|
||||
let arrayValue = try nestedContainer.decode(T.self)
|
||||
tmpArray.append(arrayValue)
|
||||
}
|
||||
|
||||
return tmpArray
|
||||
}
|
||||
|
||||
public func decodeArrayIfPresent<T>(_ type: T.Type, forKey key: Self.Key) throws -> [T]? where T : Decodable {
|
||||
var tmpArray: [T]? = nil
|
||||
|
||||
if contains(key) {
|
||||
tmpArray = try decodeArray(T.self, forKey: key)
|
||||
}
|
||||
|
||||
return tmpArray
|
||||
}
|
||||
|
||||
public func decodeMap<T>(_ type: T.Type, excludedKeys: Set<Self.Key>) throws -> [Self.Key: T] where T : Decodable {
|
||||
var map: [Self.Key : T] = [:]
|
||||
|
||||
for key in allKeys {
|
||||
if !excludedKeys.contains(key) {
|
||||
let value = try decode(T.self, forKey: key)
|
||||
map[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
{{#usePromiseKit}}extension RequestBuilder {
|
||||
public func execute() -> Promise<Response<T>> {
|
||||
let deferred = Promise<Response<T>>.pending()
|
||||
|
||||
@@ -21,10 +21,7 @@ public enum {{classname}}: {{dataType}}, Codable {
|
||||
}
|
||||
{{/isEnum}}
|
||||
{{^isEnum}}
|
||||
{{#vars.isEmpty}}
|
||||
public typealias {{classname}} = {{dataType}}
|
||||
{{/vars.isEmpty}}
|
||||
{{^vars.isEmpty}}
|
||||
|
||||
open class {{classname}}: {{#parent}}{{{parent}}}{{/parent}}{{^parent}}Codable{{/parent}} {
|
||||
|
||||
{{#vars}}
|
||||
@@ -37,11 +34,11 @@ open class {{classname}}: {{#parent}}{{{parent}}}{{/parent}}{{^parent}}Codable{{
|
||||
{{#vars}}
|
||||
{{#isEnum}}
|
||||
{{#description}}/** {{description}} */
|
||||
{{/description}}public var {{name}}: {{{datatypeWithEnum}}}{{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}
|
||||
{{/description}}public var {{name}}: {{{datatypeWithEnum}}}{{^required}}?{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}
|
||||
{{/isEnum}}
|
||||
{{^isEnum}}
|
||||
{{#description}}/** {{description}} */
|
||||
{{/description}}public var {{name}}: {{{datatype}}}{{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{#objcCompatible}}{{#vendorExtensions.x-swift-optional-scalar}}
|
||||
{{/description}}public var {{name}}: {{{datatype}}}{{^required}}?{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{#objcCompatible}}{{#vendorExtensions.x-swift-optional-scalar}}
|
||||
public var {{name}}Num: NSNumber? {
|
||||
get {
|
||||
return {{name}}.map({ return NSNumber(value: $0) })
|
||||
@@ -51,19 +48,9 @@ open class {{classname}}: {{#parent}}{{{parent}}}{{/parent}}{{^parent}}Codable{{
|
||||
{{/vars}}
|
||||
|
||||
{{#additionalPropertiesType}}
|
||||
public var additionalProperties: [AnyHashable:{{{additionalPropertiesType}}}] = [:]
|
||||
public var additionalProperties: [String:{{{additionalPropertiesType}}}] = [:]
|
||||
|
||||
{{/additionalPropertiesType}}
|
||||
{{^unwrapRequired}}
|
||||
{{^parent}}public init() {}{{/parent}}{{/unwrapRequired}}
|
||||
{{#unwrapRequired}}
|
||||
public init({{#allVars}}{{^-first}}, {{/-first}}{{name}}: {{#isEnum}}{{datatypeWithEnum}}{{/isEnum}}{{^isEnum}}{{datatype}}{{/isEnum}}{{^required}}?=nil{{/required}}{{/allVars}}) {
|
||||
{{#vars}}
|
||||
self.{{name}} = {{name}}
|
||||
{{/vars}}
|
||||
}{{/unwrapRequired}}
|
||||
{{#additionalPropertiesType}}
|
||||
public subscript(key: AnyHashable) -> {{{additionalPropertiesType}}}? {
|
||||
public subscript(key: String) -> {{{additionalPropertiesType}}}? {
|
||||
get {
|
||||
if let value = additionalProperties[key] {
|
||||
return value
|
||||
@@ -77,12 +64,38 @@ open class {{classname}}: {{#parent}}{{{parent}}}{{/parent}}{{^parent}}Codable{{
|
||||
}
|
||||
{{/additionalPropertiesType}}
|
||||
|
||||
private enum CodingKeys: String, CodingKey { {{#vars}}
|
||||
case {{{name}}} = "{{{baseName}}}"{{/vars}}
|
||||
// Encodable protocol methods
|
||||
|
||||
public {{#parent}}override {{/parent}}func encode(to encoder: Encoder) throws {
|
||||
|
||||
var container = encoder.container(keyedBy: String.self)
|
||||
|
||||
{{#vars}}
|
||||
try container.encode{{#isListContainer}}Array{{/isListContainer}}{{^required}}IfPresent{{/required}}({{{name}}}, forKey: "{{{baseName}}}")
|
||||
{{/vars}}
|
||||
{{#additionalPropertiesType}}
|
||||
try container.encodeMap(additionalProperties)
|
||||
{{/additionalPropertiesType}}
|
||||
}
|
||||
|
||||
// Decodable protocol methods
|
||||
|
||||
public {{#parent}}override {{/parent}}required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: String.self)
|
||||
|
||||
{{#vars}}
|
||||
{{name}} = try container.decode{{#isListContainer}}Array{{/isListContainer}}{{^required}}IfPresent{{/required}}({{#isListContainer}}{{{items.datatype}}}{{/isListContainer}}{{^isListContainer}}{{{datatype}}}{{/isListContainer}}.self, forKey: "{{{baseName}}}")
|
||||
{{/vars}}
|
||||
{{#additionalPropertiesType}}
|
||||
var nonAdditionalPropertyKeys = Set<String>()
|
||||
{{#vars}}
|
||||
nonAdditionalPropertyKeys.insert("{{{baseName}}}")
|
||||
{{/vars}}
|
||||
additionalProperties = try container.decodeMap({{{additionalPropertiesType}}}.self, excludedKeys: nonAdditionalPropertyKeys)
|
||||
{{/additionalPropertiesType}}
|
||||
}
|
||||
}
|
||||
{{/vars.isEmpty}}
|
||||
|
||||
{{/isEnum}}
|
||||
{{/isArrayModel}}
|
||||
{{/model}}
|
||||
|
||||
@@ -274,6 +274,72 @@
|
||||
}
|
||||
},
|
||||
"description": "Response object containing AllPrimitives object"
|
||||
},
|
||||
"ModelWithStringAdditionalPropertiesOnly": {
|
||||
"description": "This is an empty model with no properties and only additionalProperties of type string",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"ModelWithIntAdditionalPropertiesOnly": {
|
||||
"description": "This is an empty model with no properties and only additionalProperties of type int32",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"ModelWithPropertiesAndAdditionalProperties": {
|
||||
"description": "This is an empty model with no properties and only additionalProperties of type int32",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"myIntegerReq",
|
||||
"myPrimitiveReq",
|
||||
"myStringArrayReq",
|
||||
"myPrimitiveArrayReq"
|
||||
],
|
||||
"properties": {
|
||||
"myIntegerReq": {
|
||||
"type": "integer"
|
||||
},
|
||||
"myIntegerOpt": {
|
||||
"type": "integer"
|
||||
},
|
||||
"myPrimitiveReq": {
|
||||
"$ref": "#/definitions/AllPrimitives"
|
||||
},
|
||||
"myPrimitiveOpt": {
|
||||
"$ref": "#/definitions/AllPrimitives"
|
||||
},
|
||||
"myStringArrayReq": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"myStringArrayOpt": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"myPrimitiveArrayReq": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AllPrimitives"
|
||||
}
|
||||
},
|
||||
"myPrimitiveArrayOpt": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AllPrimitives"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user