import { arrayIntercalate } from "collection-utils" ; import { assert , defined } from "../support/Support" ; import { TargetLanguage } from "../TargetLanguage" ; import { Type , ClassType , EnumType , UnionType , ArrayType , MapType , TypeKind , ClassProperty , TransformedStringTypeKind , PrimitiveStringTypeKind } from "../Type" ; import { matchType , nullableFromUnion , removeNullFromUnion } from "../TypeUtils" ; import { Name , Namer , funPrefixNamer } from "../Naming" ; import { BooleanOption , EnumOption , Option , StringOption , OptionValues , getOptionValues } from "../RendererOptions" ; import { Sourcelike , maybeAnnotated , modifySource } from "../Source" ; import { anyTypeIssueAnnotation , nullTypeIssueAnnotation } from "../Annotation" ; import { ConvenienceRenderer , ForbiddenWordsInfo } from "../ConvenienceRenderer" ; import { legalizeCharacters , isLetterOrUnderscore , isNumeric , isDigit , utf32ConcatMap , escapeNonPrintableMapper , isPrintable , intToHex , splitIntoWords , combineWords , firstUpperWordStyle , allLowerWordStyle , allUpperWordStyle , camelCase , addPrefixIfNecessary } from "../support/Strings" ; import { RenderContext , ForEachPosition } from "../Renderer" ; import { StringTypeMapping } from "../TypeBuilder" ; import { panic } from "../support/Support" ; import { DefaultDateTimeRecognizer , DateTimeRecognizer } from "../DateTime" ; import { acronymOption , acronymStyle , AcronymStyleOptions } from "../support/Acronyms" ; const MAX_SAMELINE_PROPERTIES = 4 ; export const swiftOptions = { justTypes : new BooleanOption ( "just-types" , "Plain types only" , false ) , convenienceInitializers : new BooleanOption ( "initializers" , "Generate initializers and mutators" , true ) , explicitCodingKeys : new BooleanOption ( "coding-keys" , "Explicit CodingKey values in Codable types" , true ) , urlSession : new BooleanOption ( "url-session" , "URLSession task extensions" , false ) , alamofire : new BooleanOption ( "alamofire" , "Alamofire extensions" , false ) , namedTypePrefix : new StringOption ( "type-prefix" , "Prefix for type names" , "PREFIX" , "" , "secondary" ) , useClasses : new EnumOption ( "struct-or-class" , "Structs or classes" , [ [ "struct" , false ] , [ "class" , true ] ] ) , mutableProperties : new BooleanOption ( "mutable-properties" , "Use var instead of let for object properties" , false ) , acronymStyle : acronymOption ( AcronymStyleOptions . Pascal ) , dense : new EnumOption ( "density" , "Code density" , [ [ "dense" , true ] , [ "normal" , false ] ] , "dense" , "secondary" ) , linux : new BooleanOption ( "support-linux" , "Support Linux" , false , "secondary" ) , objcSupport : new BooleanOption ( "objective-c-support" , "Objects inherit from NSObject and @objcMembers is added to classes" , false ) , swift5Support : new BooleanOption ( "swift-5-support" , "Renders output in a Swift 5 compatible mode" , false ) , multiFileOutput : new BooleanOption ( "multi-file-output" , "Renders each top-level object in its own Swift file" , false ) , accessLevel : new EnumOption ( "access-level" , "Access level" , [ [ "internal" , "internal" ] , [ "public" , "public" ] ] , "internal" , "secondary" ) , protocol : new EnumOption ( "protocol" , "Make types implement protocol" , [ [ "none" , { equatable : false , hashable : false } ] , [ "equatable" , { equatable : true , hashable : false } ] , [ "hashable" , { equatable : false , hashable : true } ] ] , "none" , "secondary" ) } ; const swiftDateTimeRegex = /^\d+-\d+-\d+T\d+:\d+:\d+([zZ]|[+-]\d+(:\d+)?)$/ ; class SwiftDateTimeRecognizer extends DefaultDateTimeRecognizer { isDateTime ( str ) { return str . match ( swiftDateTimeRegex ) !== null ; } } export interface SwiftProperty { name : Name ; jsonName : string ; parameter : ClassProperty ; position : ForEachPosition ; } export class SwiftTargetLanguage extends TargetLanguage { constructor ( ) { super ( "Swift" , [ "swift" , "swift4" ] , "swift" ) ; } protected getOptions ( ) { return [ swiftOptions . justTypes , swiftOptions . useClasses , swiftOptions . dense , swiftOptions . convenienceInitializers , swiftOptions . explicitCodingKeys , swiftOptions . accessLevel , swiftOptions . urlSession , swiftOptions . alamofire , swiftOptions . linux , swiftOptions . namedTypePrefix , swiftOptions . protocol , swiftOptions . acronymStyle , swiftOptions . objcSupport , swiftOptions . swift5Support , swiftOptions . multiFileOutput , swiftOptions . mutableProperties ] ; } get stringTypeMapping ( ) : StringTypeMapping { const mapping = new Map ( ) ; mapping . set ( "date-time" , "date-time" ) ; return mapping ; } get supportsOptionalClassProperties ( ) : boolean { return true ; } get supportsUnionsWithBothNumberTypes ( ) : boolean { return true ; } protected makeRenderer ( renderContext , untypedOptionValues ) { return new SwiftRenderer ( this , renderContext , getOptionValues ( swiftOptions , untypedOptionValues ) ) ; } get dateTimeRecognizer ( ) : DateTimeRecognizer { return new SwiftDateTimeRecognizer ( ) ; } } const keywords = [ "associatedtype" , "class" , "deinit" , "enum" , "extension" , "fileprivate" , "func" , "import" , "init" , "inout" , "internal" , "let" , "open" , "operator" , "private" , "protocol" , "public" , "static" , "struct" , "subscript" , "typealias" , "var" , "break" , "case" , "continue" , "default" , "defer" , "description" , "do" , "else" , "fallthrough" , "for" , "guard" , "if" , "in" , "repeat" , "return" , "switch" , "where" , "while" , "as" , "Any" , "catch" , "false" , "is" , "nil" , "rethrows" , "super" , "self" , "Self" , "throw" , "throws" , "true" , "try" , "_" , "associativity" , "convenience" , "dynamic" , "didSet" , "final" , "get" , "infix" , "indirect" , "lazy" , "left" , "mutating" , "nonmutating" , "optional" , "override" , "postfix" , "precedence" , "prefix" , "Protocol" , "required" , "right" , "set" , "Type" , "unowned" , "weak" , "willSet" , "String" , "Int" , "Double" , "Bool" , "Data" , "Date" , "URL" , "CommandLine" , "FileHandle" , "JSONSerialization" , "checkNull" , "removeNSNull" , "nilToNSNull" , "convertArray" , "convertOptional" , "convertDict" , "convertDouble" , "jsonString" , "jsonData" ] ; function isPartCharacter ( codePoint ) { return isLetterOrUnderscore ( codePoint ) || isNumeric ( codePoint ) ; } function isStartCharacter ( codePoint ) { return isPartCharacter ( codePoint ) && ! isDigit ( codePoint ) ; } const legalizeName = legalizeCharacters ( isPartCharacter ) ; function swiftNameStyle ( prefix , isUpper , original , acronymsStyle = allUpperWordStyle ) { const words = splitIntoWords ( original ) ; const combined = combineWords ( words , legalizeName , isUpper ? firstUpperWordStyle : allLowerWordStyle , firstUpperWordStyle , isUpper ? allUpperWordStyle : allLowerWordStyle , acronymsStyle , "" , isStartCharacter ) ; return addPrefixIfNecessary ( prefix , combined ) ; } function unicodeEscape ( codePoint ) { return "\\u{" + intToHex ( codePoint , 0 ) + "}" ; } const stringEscape = utf32ConcatMap ( escapeNonPrintableMapper ( isPrintable , unicodeEscape ) ) ; export class SwiftRenderer extends ConvenienceRenderer { private _currentFilename : string | undefined ; private _needAny : boolean = false ; private _needNull : boolean = false ; constructor ( targetLanguage , renderContext , private readonly _options ) { super ( targetLanguage , renderContext ) ; } protected forbiddenNamesForGlobalNamespace ( ) { if ( this . _options . alamofire ) { return [ "DataRequest" , ... keywords ] ; } return keywords ; } protected forbiddenForObjectProperties ( _c , _classNamed ) { return { names : [ "fromURL" , "json" ] , includeGlobalForbidden : true } ; } protected forbiddenForEnumCases ( _e , _enumName ) { return { names : [ ] , includeGlobalForbidden : true } ; } protected forbiddenForUnionMembers ( _u , _unionName ) { return { names : [ ] , includeGlobalForbidden : true } ; } protected makeNamedTypeNamer ( ) { return funPrefixNamer ( "upper" , s => swiftNameStyle ( this . _options . namedTypePrefix , true , s , acronymStyle ( this . _options . acronymStyle ) ) ) ; } protected namerForObjectProperty ( ) { return this . lowerNamingFunction ; } protected makeUnionMemberNamer ( ) { return this . lowerNamingFunction ; } protected makeEnumCaseNamer ( ) { return this . lowerNamingFunction ; } protected isImplicitCycleBreaker ( t ) { const kind = t . kind ; return kind === "array" || kind === "map" ; } protected emitDescriptionBlock ( lines ) { this . emitCommentLines ( lines , "/// " ) ; } private emitBlock ( line , f ) { this . emitLine ( line , " {" ) ; this . indent ( f ) ; this . emitLine ( "}" ) ; } private emitBlockWithAccess ( line , f ) { this . emitBlock ( [ this . accessLevel , line ] , f ) ; } private justTypesCase ( justTypes , notJustTypes ) { if ( this . _options . justTypes ) return justTypes ; else return notJustTypes ; } private get lowerNamingFunction ( ) { return funPrefixNamer ( "lower" , s => swiftNameStyle ( "" , false , s , acronymStyle ( this . _options . acronymStyle ) ) ) ; } protected swiftPropertyType ( p ) { if ( p . isOptional ) { return [ this . swiftType ( p . type , true , true ) , "?" ] ; } else { return this . swiftType ( p . type , true ) ; } } protected swiftType ( t , withIssues = false , noOptional = false ) { const optional = noOptional ? "" : "?" ; return matchType < Sourcelike > ( t , _anyType => { this . _needAny = true ; return maybeAnnotated ( withIssues , anyTypeIssueAnnotation , this . justTypesCase ( [ "Any" , optional ] , "JSONAny" ) ) ; } , _nullType => { this . _needNull = true ; return maybeAnnotated ( withIssues , nullTypeIssueAnnotation , this . justTypesCase ( "NSNull" , [ "JSONNull" , optional ] ) ) ; } , _boolType => "Bool" , _integerType => "Int" , _doubleType => "Double" , _stringType => "String" , arrayType => [ "[" , this . swiftType ( arrayType . items , withIssues ) , "]" ] , classType => this . nameForNamedType ( classType ) , mapType => [ "[String: " , this . swiftType ( mapType . values , withIssues ) , "]" ] , enumType => this . nameForNamedType ( enumType ) , unionType => { const nullable = nullableFromUnion ( unionType ) ; if ( nullable !== null ) return [ this . swiftType ( nullable , withIssues ) , optional ] ; return this . nameForNamedType ( unionType ) ; } , transformedStringType => { if ( transformedStringType . kind === "date-time" ) { return "Date" ; } else { return panic ( `Transformed string type ${ transformedStringType . kind } not supported` ) ; } } ) ; } protected proposedUnionMemberNameForTypeKind ( kind ) { if ( kind === "enum" ) { return "enumeration" ; } if ( kind === "union" ) { return "one_of" ; } return null ; } private renderSingleFileHeaderComments ( ) { this . emitLineOnce ( "// This file was generated from JSON Schema using quicktype, do not modify it directly." ) ; this . emitLineOnce ( "// To parse the JSON, add this file to your project and do:" ) ; this . emitLineOnce ( "//" ) ; this . forEachTopLevel ( "none" , ( t , topLevelName ) => { if ( this . _options . convenienceInitializers && ! ( t instanceof EnumType ) ) { this . emitLineOnce ( "//   let " , modifySource ( camelCase , topLevelName ) , " = try " , topLevelName , "(json)" ) ; } else { this . emitLineOnce ( "//   let " , modifySource ( camelCase , topLevelName ) , " = " , "try? JSONDecoder().decode(" , topLevelName , ".self, from: jsonData)" ) ; } } ) ; } private renderHeader ( type , name ) { if ( this . leadingComments !== undefined ) { this . emitCommentLines ( this . leadingComments ) ; } else if ( ! this . _options . justTypes ) { if ( this . _options . multiFileOutput ) { this . emitLineOnce ( "// This file was generated from JSON Schema using quicktype, do not modify it directly." ) ; this . emitLineOnce ( "// To parse the JSON, add this file to your project and do:" ) ; this . emitLineOnce ( "//" ) ; if ( this . _options . convenienceInitializers && ! ( type instanceof EnumType ) ) { this . emitLine ( "//   let " , modifySource ( camelCase , name ) , " = try " , name , "(json)" ) ; } else { this . emitLine ( "//   let " , modifySource ( camelCase , name ) , " = " , "try? newJSONDecoder().decode(" , name , ".self, from: jsonData)" ) ; } } if ( this . _options . urlSession ) { this . emitLine ( "//" ) ; this . emitLine ( "// To read values from URLs:" ) ; const lowerName = modifySource ( camelCase , name ) ; this . emitLine ( "//" ) ; this . emitLine ( "//   let task = URLSession.shared." , lowerName , "Task(with: url) { " , lowerName , ", response, error in" ) ; this . emitLine ( "//     if let " , lowerName , " = " , lowerName , " {" ) ; this . emitLine ( "//       ..." ) ; this . emitLine ( "//     }" ) ; this . emitLine ( "//   }" ) ; this . emitLine ( "//   task.resume()" ) ; } if ( this . _options . alamofire ) { this . emitLine ( "//" ) ; this . emitLine ( "// To parse values from Alamofire responses:" ) ; this . emitLine ( "//" ) ; this . emitLine ( "//   Alamofire.request(url).response" , name , " { response in" ) ; this . emitLine ( "//     if let " , modifySource ( camelCase , name ) , " = response.result.value {" ) ; this . emitLine ( "//       ..." ) ; this . emitLine ( "//     }" ) ; this . emitLine ( "//   }" ) ; } if ( this . _options . protocol . hashable || this . _options . protocol . equatable ) { this . emitLine ( "//" ) ; this . emitLine ( "// Hashable or Equatable:" ) ; this . emitLine ( "// The compiler will not be able to synthesize the implementation of Hashable or Equatable" ) ; this . emitLine ( "// for types that require the use of JSONAny, nor will the implementation of Hashable be" ) ; this . emitLine ( "// synthesized for types that have collections (such as arrays or dictionaries)." ) ; } } this . ensureBlankLine ( ) ; this . emitLineOnce ( "import Foundation" ) ; if ( ! this . _options . justTypes && this . _options . alamofire ) { this . emitLineOnce ( "import Alamofire" ) ; } this . ensureBlankLine ( ) ; } private renderTopLevelAlias ( t , name ) { this . emitLine ( this . accessLevel , "typealias " , name , " = " , this . swiftType ( t , true ) ) ; } protected getProtocolsArray ( _t , isClass ) { const protocols = [ ] ; if ( isClass && this . _options . objcSupport ) { protocols . push ( "NSObject" ) ; } if ( ! this . _options . justTypes ) { protocols . push ( "Codable" ) ; } if ( this . _options . protocol . hashable ) { protocols . push ( "Hashable" ) ; } if ( this . _options . protocol . equatable ) { protocols . push ( "Equatable" ) ; } return protocols ; } private getProtocolString ( _t , isClass ) { const protocols = this . getProtocolsArray ( _t , isClass ) ; return protocols . length > 0 ? ": " + protocols . join ( ", " ) : "" ; } private getEnumPropertyGroups ( c ) { type PropertyGroup = { name : Name ; label ? : string ; } [ ] ; let groups = [ ] ; let group = [ ] ; this . forEachClassProperty ( c , "none" , ( name , jsonName ) => { const label = stringEscape ( jsonName ) ; const redundant = this . sourcelikeToString ( name ) === label ; if ( this . _options . dense && redundant ) { group . push ( { name } ) ; } else { if ( group . length > 0 ) { groups . push ( group ) ; group = [ ] ; } groups . push ( [ { name , label } ] ) ; } } ) ; if ( group . length > 0 ) { groups . push ( group ) ; } return groups ; } private get accessLevel ( ) : string { return this . _options . accessLevel === "internal" ? "" : this . _options . accessLevel + " " ; } private get objcMembersDeclaration ( ) : string { if ( this . _options . objcSupport ) { return "@objcMembers " ; } return "" ; } protected startFile ( basename ) { if ( this . _options . multiFileOutput === false ) { return ; } assert ( this . _currentFilename === undefined , "Previous file wasn't finished: " + this . _currentFilename ) ; this . _currentFilename = `${ this . sourcelikeToString ( basename ) }.swift` ; this . initializeEmitContextForFilename ( this . _currentFilename ) ; } protected endFile ( ) { if ( this . _options . multiFileOutput === false ) { return ; } this . finishFile ( defined ( this . _currentFilename ) ) ; this . _currentFilename = undefined ; } protected propertyLinesDefinition ( name , parameter ) { const useMutableProperties = this . _options . mutableProperties ; return [ this . accessLevel , useMutableProperties ? "var " : "let " , name , ": " , this . swiftPropertyType ( parameter ) ] ; } private renderClassDefinition ( c , className ) { this . startFile ( className ) ; this . renderHeader ( c , className ) ; this . emitDescription ( this . descriptionForType ( c ) ) ; this . emitMark ( this . sourcelikeToString ( className ) , true ) ; const isClass = this . _options . useClasses || this . isCycleBreakerType ( c ) ; const structOrClass = isClass ? "class" : "struct" ; if ( isClass && this . _options . objcSupport ) { this . emitItem ( this . objcMembersDeclaration ) ; } this . emitBlockWithAccess ( [ structOrClass , " " , className , this . getProtocolString ( c , isClass ) ] , ( ) => { if ( this . _options . dense ) { let lastProperty = undefined ; let lastNames = [ ] ; const emitLastProperty = ( ) => { if ( lastProperty === undefined ) return ; const useMutableProperties = this . _options . mutableProperties ; let sources = [ [ this . accessLevel , useMutableProperties ? "var " : "let " ] ] ; lastNames . forEach ( ( n , i ) => { if ( i > 0 ) sources . push ( ", " ) ; sources . push ( n ) ; } ) ; sources . push ( ": " ) ; sources . push ( this . swiftPropertyType ( lastProperty ) ) ; this . emitLine ( sources ) ; lastProperty = undefined ; lastNames = [ ] ; } ; this . forEachClassProperty ( c , "none" , ( name , jsonName , p ) => { const description = this . descriptionForClassProperty ( c , jsonName ) ; if ( ! p . equals ( lastProperty ) || lastNames . length >= MAX_SAMELINE_PROPERTIES || description !== undefined ) { emitLastProperty ( ) ; } if ( lastProperty === undefined ) { lastProperty = p ; } lastNames . push ( name ) ; if ( description !== undefined ) { this . emitDescription ( description ) ; emitLastProperty ( ) ; } } ) ; emitLastProperty ( ) ; } else { this . forEachClassProperty ( c , "none" , ( name , jsonName , p ) => { const description = this . descriptionForClassProperty ( c , jsonName ) ; const propertyLines = this . propertyLinesDefinition ( name , p ) ; this . emitDescription ( description ) ; this . emitLine ( propertyLines ) ; } ) ; } if ( ! this . _options . justTypes ) { const groups = this . getEnumPropertyGroups ( c ) ; const allPropertiesRedundant = groups . every ( group => { return group . every ( p => p . label === undefined ) ; } ) ; if ( ! allPropertiesRedundant && c . getProperties ( ) . size > 0 ) { this . ensureBlankLine ( ) ; this . emitBlock ( "enum CodingKeys: String, CodingKey" , ( ) => { for ( const group of groups ) { const { name , label } = group [ 0 ] ; if ( this . _options . explicitCodingKeys && label !== undefined ) { this . emitLine ( "case " , name , ' = "' , label , '"' ) ; } else { const names = arrayIntercalate < Sourcelike > ( ", " , group . map ( p => p . name ) ) ; this . emitLine ( "case " , names ) ; } } } ) ; } } if ( isClass || this . _options . accessLevel === "public" ) { this . ensureBlankLine ( ) ; let initProperties = this . initializableProperties ( c ) ; let propertiesLines = [ ] ; for ( let property of initProperties ) { if ( propertiesLines . length > 0 ) propertiesLines . push ( ", " ) ; propertiesLines . push ( property . name , ": " , this . swiftPropertyType ( property . parameter ) ) ; } if ( this . propertyCount ( c ) === 0 && this . _options . objcSupport ) { this . emitBlockWithAccess ( [ "override init()" ] , ( ) => { "" ; } ) ; } else { this . emitBlockWithAccess ( [ "init(" , ... propertiesLines , ")" ] , ( ) => { for ( let property of initProperties ) { this . emitLine ( "self." , property . name , " = " , property . name ) ; } } ) ; } } } ) ; if ( ! this . _options . justTypes ) { if ( this . _options . convenienceInitializers ) { this . ensureBlankLine ( ) ; this . emitMark ( this . sourcelikeToString ( className ) + " convenience initializers and mutators" ) ; this . ensureBlankLine ( ) ; this . emitConvenienceInitializersExtension ( c , className ) ; this . ensureBlankLine ( ) ; } } this . endFile ( ) ; } protected initializableProperties ( c ) { const properties = [ ] ; this . forEachClassProperty ( c , "none" , ( name , jsonName , parameter , position ) => { const property = { name , jsonName , parameter , position } ; properties . push ( property ) ; } ) ; return properties ; } private emitNewEncoderDecoder ( ) { this . emitBlock ( "func newJSONDecoder() -> JSONDecoder" , ( ) => { this . emitLine ( "let decoder = JSONDecoder()" ) ; if ( ! this . _options . linux ) { this . emitBlock ( "if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *)" , ( ) => { this . emitLine ( "decoder.dateDecodingStrategy = .iso8601" ) ; } ) ; } else { this . emitMultiline ( `decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
    let container = try decoder.singleValueContainer()
    let dateStr = try container.decode(String.self)

    let formatter = DateFormatter()
    formatter.calendar = Calendar(identifier: .iso8601)
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.timeZone = TimeZone(secondsFromGMT: 0)
    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
    if let date = formatter.date(from: dateStr) {
        return date
    }
    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
    if let date = formatter.date(from: dateStr) {
        return date
    }
    throw DecodingError.typeMismatch(Date.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode date"))
})` ) ; } this . emitLine ( "return decoder" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( "func newJSONEncoder() -> JSONEncoder" , ( ) => { this . emitLine ( "let encoder = JSONEncoder()" ) ; if ( ! this . _options . linux ) { this . emitBlock ( "if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *)" , ( ) => { this . emitLine ( "encoder.dateEncodingStrategy = .iso8601" ) ; } ) ; } else { this . emitMultiline ( `let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
encoder.dateEncodingStrategy = .formatted(formatter)` ) ; } this . emitLine ( "return encoder" ) ; } ) ; } private emitConvenienceInitializersExtension ( c , className ) { const isClass = this . _options . useClasses || this . isCycleBreakerType ( c ) ; const convenience = isClass ? "convenience " : "" ; this . emitBlockWithAccess ( [ "extension " , className ] , ( ) => { if ( isClass ) { this . emitBlock ( "convenience init(data: Data) throws" , ( ) => { if ( this . propertyCount ( c ) > 0 ) { this . emitLine ( "let me = try newJSONDecoder().decode(" , this . swiftType ( c ) , ".self, from: data)" ) ; } else { this . emitLine ( "let _ = try newJSONDecoder().decode(" , this . swiftType ( c ) , ".self, from: data)" ) ; } let args = [ ] ; this . forEachClassProperty ( c , "none" , name => { if ( args . length > 0 ) args . push ( ", " ) ; args . push ( name , ": " , "me." , name ) ; } ) ; this . emitLine ( "self.init(" , ... args , ")" ) ; } ) ; } else { this . emitBlock ( "init(data: Data) throws" , ( ) => { this . emitLine ( "self = try newJSONDecoder().decode(" , this . swiftType ( c ) , ".self, from: data)" ) ; } ) ; } this . ensureBlankLine ( ) ; this . emitBlock ( [ convenience , "init(_ json: String, using encoding: String.Encoding = .utf8) throws" ] , ( ) => { this . emitBlock ( "guard let data = json.data(using: encoding) else" , ( ) => { this . emitLine ( `throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)` ) ; } ) ; this . emitLine ( "try self.init(data: data)" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( [ convenience , `init(fromURL url: URL) throws` ] , ( ) => { this . emitLine ( "try self.init(data: try Data(contentsOf: url))" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitConvenienceMutator ( c , className ) ; this . ensureBlankLine ( ) ; this . emitBlock ( `func jsonData() throws -> Data` , ( ) => { this . emitLine ( "return try newJSONEncoder().encode(self)" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( `func jsonString(encoding: String.Encoding = .utf8) throws -> String?` , ( ) => { this . emitLine ( "return String(data: try self.jsonData(), encoding: encoding)" ) ; } ) ; } ) ; } private renderEnumDefinition ( e , enumName ) { this . startFile ( enumName ) ; this . emitLineOnce ( "import Foundation" ) ; this . ensureBlankLine ( ) ; this . emitDescription ( this . descriptionForType ( e ) ) ; const protocols = [ ] ; if ( ! this . _options . justTypes ) { protocols . push ( "String" ) ; protocols . push ( "Codable" ) ; } if ( this . _options . protocol . hashable ) { protocols . push ( "Hashable" ) ; } if ( this . _options . protocol . equatable ) { protocols . push ( "Equatable" ) ; } const protocolString = protocols . length > 0 ? ": " + protocols . join ( ", " ) : "" ; if ( this . _options . justTypes ) { this . emitBlockWithAccess ( [ "enum " , enumName , protocolString ] , ( ) => { this . forEachEnumCase ( e , "none" , name => { this . emitLine ( "case " , name ) ; } ) ; } ) ; } else { this . emitBlockWithAccess ( [ "enum " , enumName , protocolString ] , ( ) => { this . forEachEnumCase ( e , "none" , ( name , jsonName ) => { this . emitLine ( "case " , name , ' = "' , stringEscape ( jsonName ) , '"' ) ; } ) ; } ) ; } this . endFile ( ) ; } private renderUnionDefinition ( u , unionName ) { this . startFile ( unionName ) ; this . emitLineOnce ( "import Foundation" ) ; this . ensureBlankLine ( ) ; function sortBy ( t ) { const kind = t . kind ; if ( kind === "class" ) return kind ; return "_" + kind ; } const renderUnionCase = ( t ) : void => { this . emitBlock ( [ "if let x = try? container.decode(" , this . swiftType ( t ) , ".self)" ] , ( ) => { this . emitLine ( "self = ." , this . nameForUnionMember ( u , t ) , "(x)" ) ; this . emitLine ( "return" ) ; } ) ; } ; this . emitDescription ( this . descriptionForType ( u ) ) ; const indirect = this . isCycleBreakerType ( u ) ? "indirect " : "" ; const [ maybeNull , nonNulls ] = removeNullFromUnion ( u , sortBy ) ; this . emitBlockWithAccess ( [ indirect , "enum " , unionName , this . getProtocolString ( u , false ) ] , ( ) => { this . forEachUnionMember ( u , nonNulls , "none" , null , ( name , t ) => { this . emitLine ( "case " , name , "(" , this . swiftType ( t ) , ")" ) ; } ) ; if ( maybeNull !== null ) { this . emitLine ( "case " , this . nameForUnionMember ( u , maybeNull ) ) ; } if ( ! this . _options . justTypes ) { this . ensureBlankLine ( ) ; this . emitBlockWithAccess ( "init(from decoder: Decoder) throws" , ( ) => { this . emitLine ( "let container = try decoder.singleValueContainer()" ) ; const boolMember = u . findMember ( "bool" ) ; if ( boolMember !== undefined ) renderUnionCase ( boolMember ) ; const integerMember = u . findMember ( "integer" ) ; if ( integerMember !== undefined ) renderUnionCase ( integerMember ) ; for ( const t of nonNulls ) { if ( t . kind === "bool" || t . kind === "integer" ) continue ; renderUnionCase ( t ) ; } if ( maybeNull !== null ) { this . emitBlock ( "if container.decodeNil()" , ( ) => { this . emitLine ( "self = ." , this . nameForUnionMember ( u , maybeNull ) ) ; this . emitLine ( "return" ) ; } ) ; } this . emitDecodingError ( unionName ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlockWithAccess ( "func encode(to encoder: Encoder) throws" , ( ) => { this . emitLine ( "var container = encoder.singleValueContainer()" ) ; this . emitLine ( "switch self {" ) ; this . forEachUnionMember ( u , nonNulls , "none" , null , ( name , _ ) => { this . emitLine ( "case ." , name , "(let x):" ) ; this . indent ( ( ) => this . emitLine ( "try container.encode(x)" ) ) ; } ) ; if ( maybeNull !== null ) { this . emitLine ( "case ." , this . nameForUnionMember ( u , maybeNull ) , ":" ) ; this . indent ( ( ) => this . emitLine ( "try container.encodeNil()" ) ) ; } this . emitLine ( "}" ) ; } ) ; } } ) ; this . endFile ( ) ; } private emitTopLevelMapAndArrayConvenienceInitializerExtensions ( t , name ) { let extensionSource ; if ( t instanceof ArrayType ) { extensionSource = [ "Array where Element == " , name , ".Element" ] ; } else if ( t instanceof MapType ) { extensionSource = [ "Dictionary where Key == String, Value == " , this . swiftType ( t . values ) ] ; } else { return ; } this . emitBlockWithAccess ( [ "extension " , extensionSource ] , ( ) => { this . emitBlock ( [ "init(data: Data) throws" ] , ( ) => { this . emitLine ( "self = try newJSONDecoder().decode(" , name , ".self, from: data)" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( "init(_ json: String, using encoding: String.Encoding = .utf8) throws" , ( ) => { this . emitBlock ( "guard let data = json.data(using: encoding) else" , ( ) => { this . emitLine ( `throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)` ) ; } ) ; this . emitLine ( "try self.init(data: data)" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( `init(fromURL url: URL) throws` , ( ) => { this . emitLine ( "try self.init(data: try Data(contentsOf: url))" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( "func jsonData() throws -> Data" , ( ) => { this . emitLine ( "return try newJSONEncoder().encode(self)" ) ; } ) ; this . ensureBlankLine ( ) ; this . emitBlock ( "func jsonString(encoding: String.Encoding = .utf8) throws -> String?" , ( ) => { this . emitLine ( "return String(data: try self.jsonData(), encoding: encoding)" ) ; } ) ; } ) ; } private emitDecodingError ( name ) { this . emitLine ( "throw DecodingError.typeMismatch(" , name , '.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for ' , name , '"))' ) ; } private emitSupportFunctions4 = ( ) : void => { this . startFile ( "JSONSchemaSupport" ) ; this . emitLineOnce ( "import Foundation" ) ; this . forEachTopLevel ( "leading" , ( t , name ) => this . renderTopLevelAlias ( t , name ) , t => this . namedTypeToNameForTopLevel ( t ) === undefined ) ; if ( this . _options . convenienceInitializers ) { this . ensureBlankLine ( ) ; this . forEachTopLevel ( "leading-and-interposing" , ( t , name ) => this . emitTopLevelMapAndArrayConvenienceInitializerExtensions ( t , name ) ) ; } if ( ( ! this . _options . justTypes && this . _options . convenienceInitializers ) || this . _options . urlSession || this . _options . alamofire ) { this . ensureBlankLine ( ) ; this . emitMark ( "Helper functions for creating encoders and decoders" , true ) ; this . ensureBlankLine ( ) ; this . emitNewEncoderDecoder ( ) ; } if ( this . _options . urlSession ) { this . ensureBlankLine ( ) ; this . emitMark ( "URLSession response handlers" , true ) ; this . ensureBlankLine ( ) ; this . emitURLSessionExtension ( ) ; } if ( this . _options . alamofire ) { this . ensureBlankLine ( ) ; this . emitMark ( "Alamofire response handlers" , true ) ; this . ensureBlankLine ( ) ; this . emitAlamofireExtension ( ) ; } if ( this . _needAny || this . _needNull ) { this . ensureBlankLine ( ) ; this . emitMark ( "Encode/decode helpers" , true ) ; this . ensureBlankLine ( ) ; if ( this . _options . objcSupport ) { this . emitLine ( this . objcMembersDeclaration , this . accessLevel , "class JSONNull: NSObject, Codable {" ) ; } else { this . emitLine ( this . accessLevel , "class JSONNull: Codable, Hashable {" ) ; } this . ensureBlankLine ( ) ; this . emitMultiline ( `    public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
        return true
    }` ) ; if ( this . _options . objcSupport === false ) { this . ensureBlankLine ( ) ; this . emitMultiline ( `    public var hashValue: Int {
        return 0
    }` ) ; if ( this . _options . swift5Support ) { this . ensureBlankLine ( ) ; this . emitMultiline ( `    public func hash(into hasher: inout Hasher) {
        // No-op
    }` ) ; } } this . ensureBlankLine ( ) ; if ( this . _options . objcSupport ) { this . emitItem ( "    override " ) ; } else { this . emitItem ( "    " ) ; } this . emitMultiline ( `public init() {}
    
    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if !container.decodeNil() {
            throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
        }
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encodeNil()
    }
}` ) ; } if ( this . _needAny ) { this . ensureBlankLine ( ) ; this . emitMultiline ( `class JSONCodingKey: CodingKey {
    let key: String
    
    required init?(intValue: Int) {
        return nil
    }
    
    required init?(stringValue: String) {
        key = stringValue
    }
    
    var intValue: Int? {
        return nil
    }
    
    var stringValue: String {
        return key
    }
}` ) ; this . ensureBlankLine ( ) ; if ( this . _options . objcSupport ) { this . emitLine ( this . objcMembersDeclaration , this . accessLevel , "class JSONAny: NSObject, Codable {" ) ; } else { this . emitLine ( this . accessLevel , "class JSONAny: Codable {" ) ; } this . ensureBlankLine ( ) ; this . emitMultiline ( `    ${ this . accessLevel }let value: Any
    
    static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
        let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
        return DecodingError.typeMismatch(JSONAny.self, context)
    }
    
    static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
        let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
        return EncodingError.invalidValue(value, context)
    }

    static func decode(from container: SingleValueDecodingContainer) throws -> Any {
        if let value = try? container.decode(Bool.self) {
            return value
        }
        if let value = try? container.decode(Int64.self) {
            return value
        }
        if let value = try? container.decode(Double.self) {
            return value
        }
        if let value = try? container.decode(String.self) {
            return value
        }
        if container.decodeNil() {
            return JSONNull()
        }
        throw decodingError(forCodingPath: container.codingPath)
    }
    
    static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
        if let value = try? container.decode(Bool.self) {
            return value
        }
        if let value = try? container.decode(Int64.self) {
            return value
        }
        if let value = try? container.decode(Double.self) {
            return value
        }
        if let value = try? container.decode(String.self) {
            return value
        }
        if let value = try? container.decodeNil() {
            if value {
                return JSONNull()
            }
        }
        if var container = try? container.nestedUnkeyedContainer() {
            return try decodeArray(from: &container)
        }
        if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
            return try decodeDictionary(from: &container)
        }
        throw decodingError(forCodingPath: container.codingPath)
    }
    
    static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
        if let value = try? container.decode(Bool.self, forKey: key) {
            return value
        }
        if let value = try? container.decode(Int64.self, forKey: key) {
            return value
        }
        if let value = try? container.decode(Double.self, forKey: key) {
            return value
        }
        if let value = try? container.decode(String.self, forKey: key) {
            return value
        }
        if let value = try? container.decodeNil(forKey: key) {
            if value {
                return JSONNull()
            }
        }
        if var container = try? container.nestedUnkeyedContainer(forKey: key) {
            return try decodeArray(from: &container)
        }
        if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
            return try decodeDictionary(from: &container)
        }
        throw decodingError(forCodingPath: container.codingPath)
    }
    
    static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
        var arr: [Any] = []
        while !container.isAtEnd {
            let value = try decode(from: &container)
            arr.append(value)
        }
        return arr
    }

    static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
        var dict = [String: Any]()
        for key in container.allKeys {
            let value = try decode(from: &container, forKey: key)
            dict[key.stringValue] = value
        }
        return dict
    }
    
    static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
        for value in array {
            if let value = value as? Bool {
                try container.encode(value)
            } else if let value = value as? Int64 {
                try container.encode(value)
            } else if let value = value as? Double {
                try container.encode(value)
            } else if let value = value as? String {
                try container.encode(value)
            } else if value is JSONNull {
                try container.encodeNil()
            } else if let value = value as? [Any] {
                var container = container.nestedUnkeyedContainer()
                try encode(to: &container, array: value)
            } else if let value = value as? [String: Any] {
                var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
                try encode(to: &container, dictionary: value)
            } else {
                throw encodingError(forValue: value, codingPath: container.codingPath)
            }
        }
    }
    
    static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
        for (key, value) in dictionary {
            let key = JSONCodingKey(stringValue: key)!
            if let value = value as? Bool {
                try container.encode(value, forKey: key)
            } else if let value = value as? Int64 {
                try container.encode(value, forKey: key)
            } else if let value = value as? Double {
                try container.encode(value, forKey: key)
            } else if let value = value as? String {
                try container.encode(value, forKey: key)
            } else if value is JSONNull {
                try container.encodeNil(forKey: key)
            } else if let value = value as? [Any] {
                var container = container.nestedUnkeyedContainer(forKey: key)
                try encode(to: &container, array: value)
            } else if let value = value as? [String: Any] {
                var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
                try encode(to: &container, dictionary: value)
            } else {
                throw encodingError(forValue: value, codingPath: container.codingPath)
            }
        }
    }

    static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
        if let value = value as? Bool {
            try container.encode(value)
        } else if let value = value as? Int64 {
            try container.encode(value)
        } else if let value = value as? Double {
            try container.encode(value)
        } else if let value = value as? String {
            try container.encode(value)
        } else if value is JSONNull {
            try container.encodeNil()
        } else {
            throw encodingError(forValue: value, codingPath: container.codingPath)
        }
    }
    
    public required init(from decoder: Decoder) throws {
        if var arrayContainer = try? decoder.unkeyedContainer() {
            self.value = try JSONAny.decodeArray(from: &arrayContainer)
        } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
            self.value = try JSONAny.decodeDictionary(from: &container)
        } else {
            let container = try decoder.singleValueContainer()
            self.value = try JSONAny.decode(from: container)
        }
    }
    
    public func encode(to encoder: Encoder) throws {
        if let arr = self.value as? [Any] {
            var container = encoder.unkeyedContainer()
            try JSONAny.encode(to: &container, array: arr)
        } else if let dict = self.value as? [String: Any] {
            var container = encoder.container(keyedBy: JSONCodingKey.self)
            try JSONAny.encode(to: &container, dictionary: dict)
        } else {
            var container = encoder.singleValueContainer()
            try JSONAny.encode(to: &container, value: self.value)
        }
    }
}` ) ; } this . endFile ( ) ; } ; private emitConvenienceMutator ( c , className ) { this . emitLine ( "func with(" ) ; this . indent ( ( ) => { this . forEachClassProperty ( c , "none" , ( name , _ , p , position ) => { this . emitLine ( name , ": " , this . swiftPropertyType ( p ) , "? = nil" , position !== "only" && position !== "last" ? "," : "" ) ; } ) ; } ) ; this . emitBlock ( [ ") -> " , className ] , ( ) => { this . emitLine ( "return " , className , "(" ) ; this . indent ( ( ) => { this . forEachClassProperty ( c , "none" , ( name , _ , _p , position ) => { this . emitLine ( name , ": " , name , " ?? self." , name , position !== "only" && position !== "last" ? "," : "" ) ; } ) ; } ) ; this . emitLine ( ")" ) ; } ) ; } protected emitMark ( line , horizontalLine = false ) { this . emitLine ( "// MARK:" , horizontalLine ? " - " : " " , line ) ; } protected emitSourceStructure ( ) { if ( this . _options . multiFileOutput === false ) { this . renderSingleFileHeaderComments ( ) ; } this . forEachNamedType ( "leading-and-interposing" , ( c , className ) => this . renderClassDefinition ( c , className ) , ( e , enumName ) => this . renderEnumDefinition ( e , enumName ) , ( u , unionName ) => this . renderUnionDefinition ( u , unionName ) ) ; if ( ! this . _options . justTypes ) { this . emitSupportFunctions4 ( ) ; } } private emitURLSessionExtension ( ) { this . ensureBlankLine ( ) ; this . emitBlockWithAccess ( "extension URLSession" , ( ) => { this . emitMultiline ( `fileprivate func codableTask<T: Codable>(with url: URL, completionHandler: @escaping (T?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
    return self.dataTask(with: url) { data, response, error in
        guard let data = data, error == nil else {
            completionHandler(nil, response, error)
            return
        }
        completionHandler(try? newJSONDecoder().decode(T.self, from: data), response, nil)
    }
}` ) ; this . ensureBlankLine ( ) ; this . forEachTopLevel ( "leading-and-interposing" , ( _ , name ) => { this . emitBlock ( [ "func " , modifySource ( camelCase , name ) , "Task(with url: URL, completionHandler: @escaping (" , name , "?, URLResponse?, Error?) -> Void) -> URLSessionDataTask" ] , ( ) => { this . emitLine ( `return self.codableTask(with: url, completionHandler: completionHandler)` ) ; } ) ; } ) ; } ) ; } private emitAlamofireExtension ( ) { this . ensureBlankLine ( ) ; this . emitBlockWithAccess ( "extension DataRequest" , ( ) => { this . emitMultiline ( `fileprivate func decodableResponseSerializer<T: Decodable>() -> DataResponseSerializer<T> {
    return DataResponseSerializer { _, response, data, error in
        guard error == nil else { return .failure(error!) }
        
        guard let data = data else {
            return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
        }
        
        return Result { try newJSONDecoder().decode(T.self, from: data) }
    }
}

@discardableResult
fileprivate func responseDecodable<T: Decodable>(queue: DispatchQueue? = nil, completionHandler: @escaping (DataResponse<T>) -> Void) -> Self {
    return response(queue: queue, responseSerializer: decodableResponseSerializer(), completionHandler: completionHandler)
}` ) ; this . ensureBlankLine ( ) ; this . forEachTopLevel ( "leading-and-interposing" , ( _ , name ) => { this . emitLine ( "@discardableResult" ) ; this . emitBlock ( [ "func response" , name , "(queue: DispatchQueue? = nil, completionHandler: @escaping (DataResponse<" , name , ">) -> Void) -> Self" ] , ( ) => { this . emitLine ( `return responseDecodable(queue: queue, completionHandler: completionHandler)` ) ; } ) ; } ) ; } ) ; } }