Description
Previous ID | SR-7109 |
Radar | None |
Original Reporter | pdwilson12 (JIRA User) |
Type | Bug |
Additional Detail from JIRA
Votes | 0 |
Component/s | Foundation |
Labels | Bug |
Assignee | @itaiferber |
Priority | Medium |
md5: cbee189340585f8ddc888fbf34a8951f
Issue Description:
When decoding a structured JSON file in which a particular field can be either an array or a dictionary, I detect which is the case by asking the decoder for the unkeyed container and then if it generates an exception, try the keyed container:
{{ for key in keyedContainer.allKeys {}}
{{ switch key {}}
{{ case .items:}}
{{ var nested = try? keyedContainer.nestedUnkeyedContainer(forKey: key)}}
{{ if nested == nil {}}
{{ let singleItem = try? keyedContainer.decode(JSRefWrapper.self, forKey: key)}}
After this code runs and hits an exception in the first "try?", the data returned from the decoder's codingPath incorrectly lists the "items" element twice.
Looking at the implementation of JSONDecoder, I found this code multiple times in JSONEncoder.swift:
{{ fileprivate func with<T>(pushedKey key: CodingKey, _ work: () throws -> T) rethrows -> T {}}
{{ self.codingPath.append(key)}}
{{ let ret: T = try work()}}
{{ self.codingPath.removeLast()}}
{{ return ret}}
{{ }}}
If the work() block throws an exception (as is done by nestedUnkeyedContainer()), the removeLast() call will not be executed.
I think removeLast should be put into a 'defer' block, which will cause it to be executed even on an exception...
{{ fileprivate func with<T>(pushedKey key: CodingKey, _ work: () throws -> T) rethrows -> T {}}
{{ self.codingPath.append(key)}}
{{ defer { self.codingPath.removeLast()}}
{{ let ret: T = try work()}}
{{ return ret}}
{{ }}}