Search code examples
jsonswiftstringcodablejsondecoder

Split a single string into several portions when decoding a JSON (Swift)


I have a JSON file containing around 3000 unique id codes of the following format (10 digits):

1012111000

I am currently importing it into a simple struct of the following format:

struct Codes: Codable, Identifiable {
  let id: String
}

using the following extension:

extension Bundle {
  func decode<T: Codable>( file: String) -> T {
    guard let url = self.url(forResource: file, withExtension: nil) else {
      fatalError("Failred to locate \(file) in bundle")
    }
    guard let data = try? Data(contentsOf: url) else {
      fatalError("Failred to load \(file) from bundle")
    }
    let decoder = JSONDecoder()
    guard let loaded = try? decoder.decode(T.self, from: data) else {
      fatalError("Failed to decode \(file) from bundle")
    }
    return loaded
  }
}

and call

let code: [Codes] = Bundle.main.decode(file: "codes.json")

This works as expected and the data is available in Swift.
But.
The id code is actually made up of 4 individual codes. The first 3 characters are one 3-digit code, the 4th character is another single-digit code, the 5th and 6th characters form a third 2-digit code and the last four characters form a final 4-digit code. As such I'd like to instead import into a struct as follows: Note in this that id4 is unique but id1, id2 and id3 will have duplicates.

struct Codes: Codable, Identifiable {
  let id1: String    // 1st-3rd character (3 digits)
  let id2: String    // 4th character (1 digit)
  let id3: String    // 5th-6th character (2 digits)
  let id4: String    // 7th-10th characters (4 digits)
}

Any advice on how to achieve this in a nice neat way would be appreciated. I know there are numerous ways of parsing strings but I'm not sure how best to acomplish this within a loop. I should also note that the decoder is generic due to it needing to import a number of other (simpler) JSONs and this functionality needs to remain.

best, Sy


Solution

  • to make "Codes" compatible with what you already have and have the component ids, try this approach, or something similar:

    struct Codes: Codable, Identifiable {
        let id: String
        
        func id1() -> String { string(from: 0, to: 3) }
        func id2() -> String { string(from: 3, to: 4) }
        func id3() -> String { string(from: 5, to: 7) }
        func id4() -> String { string(from: 6, to: 10) }
    
       // or using lazy var
       // lazy var id1: String = { string(from: 0, to: 3) }()
       // lazy var id2: String = { string(from: 3, to: 4) }()
       // lazy var id3: String = { string(from: 5, to: 7) }()
       // lazy var id4: String = { string(from: 6, to: 10) }()
        
        private func string(from: Int, to: Int) -> String {
            let start = id.index(id.startIndex, offsetBy: from)
            let end = id.index(id.startIndex, offsetBy: to)
            return String(id[start..<end])
        }
    }
    

    Note, when using lazy var you need to declare:

    var code = Codes(id: "1012111000")
    

    not

    let code = Codes(id: "1012111000")
    

    as with the functions.