skip to Main Content

I have a JSON file like the following:

[
 {
  "id": 123,
  "textEN": "something",
  "textDE": "irgendwas"
 }, 
 ...
]

And I do have a struct in my environment:

struct Something: Codable, Identifiable, Hashable {
 var id: Int
 var text: String
}

What is the best and/or simplest way to only parse the language the user is using? I can check the users set language by using Locale.current.language.languageCode?.identifier. But how should I always only parse the specific language?
I thought about the following approaches but stumbled across some problems:

  1. Parse all different languages and use a switch case within the view. Would work but is really really bad code in my opinion.
  2. Parse all different languages and implement another logical layer before the view. Before the data is being used by the view, a manager or view model or whatever will take care of removing all translations except the users one. Should work but is there a better option?
  3. Somehow only parse and store the one language from the data that is currently being used. Probably the best solution, but I was not able to get this one working. I tried to implement a CodingKey that’s a concatenated string ("text" + language code) but it is not possible, or at least I wasn’t able to figure out how, to use a variable as a CodingKey.

2

Answers


  1. To parse only the language that the user is using, you can use the init(from decoder: Decoder) method of your Decodable struct to selectively decode only the language the user has set.

    Here is an example implementation:

    let json = """
    {
      "id": 123,
      "textEN": "something",
      "textDE": "irgendwas"
    }
    """.data(using: .utf8)!
    
    struct Something: Decodable, Identifiable, Hashable {
        var id: Int
        var text: String
        
        enum CodingKeys: String, CodingKey {
            case id, textEN, textDE
        }
    
        init(from decoder: Decoder) throws {
            
            // change language value with `Locale.current.languageCode?.identifier`
            let language = "en"
            
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.id = try container.decode(Int.self, forKey: .id)
            
            let textKeyForLanguage = language == "en" ? CodingKeys.textEN : CodingKeys.textDE
            self.text = try container.decode(String.self, forKey: textKeyForLanguage)
        }
    }
    

    In this implementation, you use the CodingKeys enum to define the keys for the properties in the JSON file. Then, in the init(from decoder: Decoder) method, you first get the user’s selected language, and then use it to selectively decode only the property that corresponds to the selected language.

    You can replace the default language value with Locale.current.language.languageCode?.identifier to get the user’s selected language, but make sure to handle cases where the user’s language is not set or recognized.

    Note that I conformed to Decodable instead of Codable , so if you need it to be Encodable also you gonna have to implement func encode(to encoder: Encoder) throws.

    Hope this helps!

    Login or Signup to reply.
  2. You could try this approach, using a dynamic key AnyKey (as mentioned in the comment and link),
    a init(from decoder: Decoder) and a langKey to read only the language you want.

    The example code shows a very simple way
    to read the json data with the use of a class LingoModel: ObservableObject.
    Adjust the code for your own purpose.

    import Foundation
    import SwiftUI
    
    class LingoModel: ObservableObject {
        @Published var langs = [Lingo]()
        
        static var langKey = "textEN"
        
        func fetchData(for lang: String) {
            LingoModel.langKey = lang
            
            let json = """
    [
    {
    "id": 123,
    "textEN": "something",
    "textDE": "irgendwas"
    },
    {
    "id": 124,
    "textEN": "something2",
    "textDE": "irgendwas2"
    },
    {
    "id": 125,
    "textEN": "something2",
    "textDE": "irgendwas2",
    "textJA": "日本語"
    }
    ]
    """
            do {
                // simulated data from an API
                let data = Data(json.utf8)
                langs = try JSONDecoder().decode([Lingo].self, from: data)
            } catch {
                print(error)
            }
        }
    }
    
    struct ContentView: View {
        @StateObject var model = LingoModel()
        @State var selected: String = "textEN"
        
        var body: some View {
            VStack {
                Picker("", selection: $selected) {
                    Text("textEN").tag("textEN")
                    Text("textDE").tag("textDE")
                    Text("textJA").tag("textJA")
                }.pickerStyle(.segmented).frame(width: 222)
                    .onChange(of: selected) { lang in
                        model.fetchData(for: lang)
                    }
                List(model.langs) { lingo in
                    Text(lingo.text)
                }
            }
            .onAppear {
                model.fetchData(for: selected)
            }
        }
        
    }
    
    struct Lingo: Codable, Identifiable, Hashable {
        var id: Int
        var text: String
        
        struct AnyKey: CodingKey {
            var stringValue: String
            var intValue: Int?
            
            init?(stringValue: String) {  self.stringValue = stringValue  }
            init?(intValue: Int) { return nil } // not used
        }
        
        init(from decoder: Decoder) throws {
            let container1 = try decoder.container(keyedBy: CodingKeys.self)
            self.id = try container1.decode(Int.self, forKey: .id)
            
            self.text = ""
            let container2 = try decoder.container(keyedBy: AnyKey.self)
            
            if let theKey = container2.allKeys.first(where: {$0.stringValue == LingoModel.langKey}),
               let txt = try? container2.decode(String.self, forKey: theKey) {
                self.text = txt
            }
        }
        
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search