I am using the following code which has no errors but I still get an expection error and not sure why:
fun getJsonDataFromAsset(context: Context, fileName: String): String? {
val jsonString: String
try {
jsonString = context.assets.open(fileName).bufferedReader().use { it.readText() }
} catch (ioException: IOException) {
ioException.printStackTrace()
return null
}
return jsonString
}
You can use Gson and AssetsManager for extracting json from asset-file. For example:
private fun createGson() = GsonBuilder().create()
fun getJsonStringFromAssets(fileName: String, context:Context): String {
val gson = createGson()
return gson.fromJson<Response>(getAssetsFileAsString(fileName, context), object : TypeToken<Response>() {}.type)
}
private fun getAssetsFileAsString(path: String, context:Context): String? {
return context.assets?.fileAsString(path)
}
private fun AssetManager.fileAsString(filename: String): String {
return open(filename).use {
it.readBytes().toString(Charset.defaultCharset())
}
}
Where 'Response' is your json data class.
public struct CodeAndDetails: Codable {
public let html: String
public var code: String
private enum CodingKeys: String, CodingKey {
case html = "DETAILS", code = "CODE"
}
public func getMessage(font: UIFont) -> NSAttributedString? {
let res = NSAttributedString(html: html, font: font)
return res
}
}
public class BaseResponse: Decodable {
enum CodingKeys: String, CodingKey {
case successDetails = "Success"
}
public let successDetails: [CodeAndDetails]
}
here:
public class CardListResponse: BaseResponse {
public var cards: [DebitCard]?
public var activeCardId: Int?
enum CodingKeys: String, CodingKey {
case cards = "row"
case activeCardId = "CurrentActiveId"
}
}
Does not decode properly. cards and activeCardId stay nil. When I change public class CardListResponse: BaseResponse to public class CardListResponse: Decodable the cards and activeCardId parse just fine (but obviously I get no payload from base class).
How could I cope with this?
In case it's not clear from the question here's sample json:
My JSON
{
"row":[
{
"PicPath":"MC"
},
{
"IsDefault":"",
"PicPath":"VISA"
}
],
"CurrentActiveId":17504322,
"Success":[
{
"DETAILS":"\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u043e",
"CODE":"1"
}
]
}
public struct DebitCard: Decodable
{
public let accountContractId: Int?
public let last8: String?
public let balanceString: String?
public let currName: String?
public let iCardId: Int?
public let isMain: Bool?
public let cardNameWithCurrencyCode: String?
public let card4: String?
public let title: String?
public let picPath: String?
enum CodingKeys: String, CodingKey {
case accountContractId = "AcntContractId"
case expire = "Expire"
case last8 = "Card8"
case balanceString = "Balance"
case currName = "CurrName"
case iCardId
case isMainString = "isMain"
case cardNameWithCurrencyCode = "CardName"
case card4 = "Card4"
case title = "CrdTInfo"
case picPath = "PicPath"
}
public init(from decoder: Decoder) throws
{
let values = try decoder.container(keyedBy: CodingKeys.self)
accountContractId = try values.decode(Int.self, forKey: .accountContractId)
last8 = try values.decode(String.self, forKey: .last8)
balanceString = try values.decode(String.self, forKey: .balanceString)
currName = try values.decode(String.self, forKey: .currName)
iCardId = try values.decode(Int.self, forKey: .iCardId)
let isMainString = try values.decode(String.self, forKey: .isMainString)
isMain = isMainString.toBool
cardNameWithCurrencyCode = try values.decode(String.self, forKey: .cardNameWithCurrencyCode)
card4 = try values.decode(String.self, forKey: .card4)
title = try values.decode(String.self, forKey: .title)
picPath = try values.decode(String.self, forKey: .picPath)
}
}
You need to implement init(from decoder: Decoder) in the subclass and call the superclass init although you don't need to implement it for the superclass
init for CardListResponse, notice the call to super.init
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
cards = try container.decode([DebitCard].self, forKey: .cards)
activeCardId = try container.decode(Int.self, forKey: .activeCardId)
try super.init(from: decoder)
}
Another option is to skip inheritance altogether and use composition instead. In the subclass you make a property for the CodeAndDetails array instead, this way you won't need a special init method anymore.
CardListResponse can then be changed to below and the super class can be removed. In the long run I think this is a more attractive solution.
public class CardListResponse: Decodable {
public var cards: [DebitCard]?
public var activeCardId: Int?
public var details: [CodeAndDetails]
private enum CodingKeys: String, CodingKey {
case cards = "row"
case activeCardId = "CurrentActiveId"
case details = "Success"
}
}
I have a JSON object, I cannot figure out how to access the exclude_list in given JSON.
{"variants":
{"variant_groups":
[
{
"group_id":"1",
"name":"Crust",
"variations":[
{"name":"Thin","price":0,"default":1,"id":"1","inStock":1},
{"name":"Thick","price":0,"default":0,"id":"2","inStock":1,"isVeg":1},
{"name":"Cheese burst","price":100,"default":0,"id":"3","inStock":1,"isVeg":1}]},
{
"group_id":"3",
"name":"Sauce",
"variations":[
{"name":"Manchurian","price":20,"default":0,"id":"20","inStock":1,"isVeg":0},
{"name":"Tomato","price":20,"default":0,"id":"21","inStock":1,"isVeg":1},
{"name":"Mustard","price":20,"default":0,"id":"22","inStock":1,"isVeg":0}]
}],
"exclude_list":[
[
{"group_id":"1","variation_id":"3"},
{"group_id":"2","variation_id":"10"}
],
[
{"group_id":"2","variation_id":"10"},
{"group_id":"3","variation_id":"22"}
]
]
}
}
I can access properties like: variants, variant_groups using below Struct:
public struct VariantResponse: Codable {
public let variants: Variants
}
public struct Variants: Codable {
public let variantGroups:[VariantGroup]
public let excludeList:[ExcludeItems]
}
public struct VariantGroup: Codable {
public let groupId: String
public let name: String
public let variations: [Variant]
}
public struct Variant: Codable {
public let name: String
public let price: Int
public let defaultValue: Int
public let id: String
public let inStock: Int
public var isVeg: Int?
private enum CodingKeys : String, CodingKey {
case name, price, defaultValue = "default", id, inStock, isVeg
}
}
public struct ExcludeItems: Codable {
public let excludes:[Exclude]
}
public struct Exclude: Codable {
public let groupId: String
public let variationId: String
}
You can try
struct VariantResponse: Codable {
let variants: Variants
}
struct Variants: Codable {
let variantGroups: [VariantGroup]
let excludeList: [[ExcludeItems]]
}
struct ExcludeItems: Codable {
let groupId, variationId: String
}
do {
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let res = try dec.decode(VariantResponse.self, from: data)
}
catch {
print(error)
}
ExcludeItems contains groupId, variationId but you add unknown key excludes
you have some problems in decodable structs , try to use this code :
public struct VariantResponse: Codable {
public let variants: Variants
}
public struct Variants: Codable {
public let variant_groups:[VariantGroup]
public let exclude_list:[[Exclude]]
}
public struct VariantGroup: Codable {
public let group_id: String
public let name: String
public let variations: [Variant]
}
public struct Variant: Codable {
public let name: String
public let price: Int
public let defaultValue: Int
public let id: String
public let inStock: Int
public var isVeg: Int?
private enum CodingKeys : String, CodingKey {
case name, price, defaultValue = "default", id, inStock, isVeg
}
}
public struct Exclude: Codable {
public let group_id: String
public let variation_id: String
}
Wishes best luck.
The issue may be with the coding keys in your model. Please refer the following Model for fetching your JSON structure.
struct VariantsResponse: Codable {
let variants: Variants?
}
typealias VariantGroups = [VariantGroup]
typealias ExcludeLists = [[ExcludeList]]
typealias Variations = [Variation]
struct Variants: Codable {
let variantGroups: VariantGroups?
let excludeList: ExcludeLists?
enum CodingKeys: String, CodingKey {
case variantGroups = "variant_groups"
case excludeList = "exclude_list"
}
}
struct ExcludeList: Codable {
let groupId, variationId: String?
enum CodingKeys: String, CodingKey {
case groupId = "group_id"
case variationId = "variation_id"
}
}
struct VariantGroup: Codable {
let groupId, name: String?
let variations: Variations?
enum CodingKeys: String, CodingKey {
case groupId = "group_id"
case name, variations
}
}
struct Variation: Codable {
let name: String?
let price, variationDefault: Int?
let id: String?
let inStock, isVeg: Int?
enum CodingKeys: String, CodingKey {
case name, price
case variationDefault = "default"
case id, inStock, isVeg
}
}
In the above model, I am creating typealias for the following arrays, for making the model more human readable and understandable.
typealias VariantGroups = [VariantGroup]
typealias ExcludeLists = [[ExcludeList]]
typealias Variations = [Variation]
here we are defining codingKeys case for variables which cannot convert directly to an attribute of the model.
Hope this will helps you to create the model.
You can simply decode the model with the responseData using the following code
let decoder = JSONDecoder()
let res = try decoder.decode(VariantsResponse.self, from: responseData)
Here if we specify the decoder.keyDecodingStrategy = .convertFromSnakeCase we didn't need to specify the codingKeys like case groupId = "group_id". The system will automatically inspect the _ character and convert the JSON key string to camelCaseKeys.
I'm attempting to parse the following json schema, poster may or may not be empty
{
"poster": {},
"recommends": []
}
My decodable classes are as follows:
public struct RecommendedList: Decodable {
public let poster: Poster?
public let recommends: [Recommend]
}
public struct Poster: Decodable {
public let backgroundImage: URL
public let topImage: URL
public let windowImage: URL
public let windowSkinImagePath: URL
public let deeplink: URL
public init(from decoder: Decoder) throws {
// I want a failable intializer not one that throws
}
}
My question is how do I make poster optional? My thought was I would need a failable initializer, but decodable requires a init that throws.
so it looks like I needed to add a try? in the Recommended List init(from decoder:)
public struct RecommendedList: Decodable {
public let poster: Poster?
public let recommends: [Recommend]
enum CodingKeys: String, CodingKey {
case poster
case recommends
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
poster = try? container.decode(Poster.self, forKey: .poster)
recommends = try container.decode([Recommend].self, forKey: .recommends)
}
}
In kotlin, how to make the setter of properties in primary constructor private?
class City(val id: String, var name: String, var description: String = "") {
fun update(name: String, description: String? = "") {
this.name = name
this.description = description ?: this.description
}
}
I want the setter of properties name to be private, and the getter of it public, how can I do?
The solution is to create a property outside of constructor and set setter's visibility.
class Sample(var id: Int, name: String) {
var name: String = name
private set
}
Update:
They're discussing it here: https://discuss.kotlinlang.org/t/private-setter-for-var-in-primary-constructor/3640
You can try like this
class Sample(var id: Int, private var name:String) {
// Backing field
var _name: String = ""
get() = name
private set
}
fun main(args: Array<String>) {
println("Hello World")
val sample = Sample(1, "hello")
// println(sample.name); It's not possible
println(sample._name)
}