2017-12-11 21 views
2

사용자 지정 유형을 JSON에 사용자 지정 방식으로 인코딩하는 것은 Swift 4에서 쉽게 수행 할 수 있으며 유용한 설명서가 있습니다. 그러나 Foundation 유형을 사용자 정의 방식으로 인코딩하는 데 문제가 있습니다. 유형 TimeInterval의 특성을 가진 내 사용자 정의 유형은 다음과 같이 분, 초를 표시하는 JSON 형식으로 Codable을 할 필요가 :맞춤 설정 방식으로 이미 코드 가능 (예 : TimeInterval)을 준수하는 기초 유형을 인코딩하는 방법은 무엇인가요?

// my type: 
struct MyType: Codable { 
    let time: TimeInterval = 65.12 
} 
// required JSON representation: 
"myType": { 
    "time": 
    { 
     "minutes": 1, 
     "seconds": 5.12 
    } 
} 

나는 각자에 대한 encode(to:)init(decoder:)의 사용자 지정 구현을 제공하는 경우는, 잘 작동 내 타입의. 하지만 나는 자신을 반복하지 않고 단순히 TimeInterval에 대해 이러한 메서드의 사용자 지정 버전을 제공하고 싶습니다. 불행하게도이 코드가 호출되지 없구요

extension TimeInterval { 
    enum CodingKeys: String, CodingKey { 
     case minutes 
     case seconds 
    } 

    func encode(to encoder: Encoder) throws { 
     let minutes = (NSInteger(self)/60) % 60 
     let seconds = self.truncatingRemainder(dividingBy: 60) 

     var container = encoder.container(keyedBy: CodingKeys.self) 
     try container.encode(minutes, forKey: .minutes) 
     try container.encode(seconds, forKey: .seconds) 
    } 

    init(from decoder: Decoder) throws { 
     let values = try decoder.container(keyedBy: CodingKeys.self) 
     guard let minutes = try? values.decode(Int.self, forKey: .minutes), let seconds = try? values.decode(Double.self, forKey: .seconds) else { 
      throw NSError() 
     } 
     self = TimeInterval(Double(minutes) * 60.0 + seconds) 
    } 
} 

:하십시오 TimeInterval 값은 항상 밖으로 상자의 Codable을 준수하는 Double을 인코딩의 기본 구현을 사용하는 것 같다 인코딩 그래서 이런 확장을 시도했다.

let myType = MyType(time: 65.12) 
let encoded = try! JSONEncoder().encode(myType) 
let json = String(data: encoded, encoding: .utf8)! 
print(json) 

// prints: 
// "myType": 
// { 
// "time": 65.12 
// } 

어떻게 해결할 수 있습니까?

편집 : 필자는 첫 번째 문장에서 쓴 것처럼 솔루션을 찾고 있지 않으며 내 사용자 지정 형식을 인코딩하는 방법을 찾고 있지 않습니다. 이것은 이미 내 코드에서 잘 작동합니다. 변경하려고 Foundation 형식 TimeInterval의 인코딩 동작을 변경하려고합니다.

답변

2

당신은 당신의 시간 간격 읽기 전용 계산 된 속성을 확인하고 TimeInterval이 소요 맞춤 이니셜을 추가하고 시간 하부 구조의 특성을 초기화 할 수 있습니다

struct Root: Codable { 
    let myType: MyType 
} 
struct MyType: Codable { 
    let time: Time 
    struct Time: Codable { 
     let minutes: Int 
     let seconds: Double 
    } 
    init(time: TimeInterval) { 
     self.time = Time(minutes: (Int(time)/60) % 60, seconds: time.truncatingRemainder(dividingBy: 60)) 
    } 
    var timeInterval: TimeInterval { 
     return TimeInterval(time.minutes * 60) + time.seconds 
    } 
} 

let root = Root(myType: MyType(time: 65.12)) 
do { 
    let encoded = try JSONEncoder().encode(root) 
    print(String(data: encoded, encoding: .utf8)!) // "{"myType":{"time":{"minutes":1,"seconds":5.1200000000000045}}}\n 
} catch { 
    print(error) 
} 

01 23,516,

편집/업데이트 : 당신은 둘 이상의 구조에 시간 속성을 추가해야하는 경우

당신은 사용자 정의 유형에서 당신의 구조 시간을 이동하고 시간 제한 프로토콜을 만들 수 있습니다

struct Time: Codable { 
    let minutes: Int 
    let seconds: Double 
} 
protocol Timed { 
    var time: Time { get } 
} 

하는 확장을 시간 제한 프로토콜 부가 TimeInterval이 읽기 전용 계산 재산

extension Timed { 
    var timeInterval: TimeInterval { 
     return TimeInterval(time.minutes * 60) + time.seconds 
    } 
} 

을 사용자 정의 초기화 연장 시간을 추가

extension Time { 
    init(time: TimeInterval) { 
     self.minutes = (Int(time)/60) % 60 
     self.seconds = time.truncatingRemainder(dividingBy: 60) 
    } 
} 

는 그리고 당신은 단지 시간 속성이 구조에 시간 초과 프로토콜을 추가해야합니다

struct Root: Codable { 
    let myType: MyType 
} 
struct MyType: Codable, Timed { 
    let time: Time 
} 

테스트 :

let root = Root(myType: MyType(time: Time(time: 65.12))) 
root.myType.timeInterval 
do { 
    let encoded = try JSONEncoder().encode(root) 
    print(String(data: encoded, encoding: .utf8)!) // "{"myType":{"time":{"minutes":1,"seconds":5.1200000000000045}}}\n 
} catch { 
    print(error) 
} 
+0

고마워요! 내가 이해하는 한, 내 유형의 TimeInterval 특성을 'Time'사용자 정의 유형으로 대체하고 필요한 특성 및 이름을 사용하는 것이 좋습니다. 아마 좋은 해결 방법 일 겁니다. 하지만 TimeInterval에서 encode (to :) 및 init (from :)을 재정의 할 수 없다는 데 동의합니까? 그게 기능 이냐 버그 야? 그것에 관한 문서가 있습니까? – Goodsquirrel

+0

코드화 가능 구조에서 encode 메서드를 구현해야합니다. TimeInterval (Double)에서 구현하지 마십시오. –

1

코드를 수정하여 (예를 들어)이 방법이 효과적입니다.

struct MyType { 
    let time: TimeInterval = 65.12 

    enum CodingKeys: String, CodingKey { 
     case minutes 
     case seconds 
    } 
} 

extension MyType: Encodable { 

    func encode(to encoder: Encoder) throws { 
     let minutes = 20 
     let seconds = 30 

     var container = encoder.container(keyedBy: CodingKeys.self) 
     try container.encode(minutes, forKey: .minutes) 
     try container.encode(seconds, forKey: .seconds) 
    } 

} 

let myType = MyType() 
let encoded = try! JSONEncoder().encode(myType) 
let json = String(data: encoded, encoding: .utf8)! 
print(json) 

enum MyCodingKeys: String, CodingKey { 
    case minutes 
    case seconds 
} 

protocol TimeIntervalEncodable: Encodable { 
    var time: TimeInterval {get set} 
} 

extension TimeIntervalEncodable { 
    func encode(to encoder: Encoder) throws { 

     let minutes = (Int(time)/60) % 60 
     let seconds = time.truncatingRemainder(dividingBy: 60) 

     var container = encoder.container(keyedBy: MyCodingKeys.self) 
     try container.encode(minutes, forKey: .minutes) 
     try container.encode(seconds, forKey: .seconds) 
    } 
} 

struct MyType: TimeIntervalEncodable { 
    var time: TimeInterval = 65.12 
} 

let myType = MyType() 
let encoded = try! JSONEncoder().encode(myType) 
let json = String(data: encoded, encoding: .utf8)! 
print(json) 
+0

예, 그냥 예. 쉽게 한. 이것은 중요한 문제가 아닙니다. 그의 시간이 몇 시간 만에 나온 것 같습니다 - 불명확 한 이유가 있습니다. –

+0

안녕하세요, 마이클, 대답 주셔서 감사합니다,하지만 내 질문에 쓴 것처럼 솔루션을 찾고 있어요, 사용자 지정 형식의 모든 사용자 지정 인코딩 방법을 별도로 작성할 필요가 없습니다. TimeInterval). 이상적인 TimeInterval 확장으로 캡슐화하고 싶습니다. – Goodsquirrel

+2

확장 프로그램은 형식에 새로운 기능을 추가 할 수 있지만 기존 기능을 재정의 할 수는 없습니다. - TimeInterval은 이미 코드 가능하므로 구현 된 메서드/이니셜 라이저를 확장으로 대체 할 수 없습니다. –