How to read data from JSON file on the resources directory?
I need to read a json file on the resources directory, convert it to a data class ("User")
I'm trying to adapt the following code
private fun getJSONFromAssets(): String? {
var json: String? = null
val charset: Charset = Charsets.UTF_8
try {
val myUsersJsonFile = assets.open("users.json")
val size = myUsersJsonFile.available()
val buffer = ByteArray(size)
myUsersJsonFile.read(buffer)
myUsersJsonFile.close()
json = String(buffer, charset)
} catch (ex: IOException) {
ex.printStackTrace()
return null
}
return json
}
but assets.open("users.json") is not recognized.
How is the best approach to read JSON files on the resources directory (mock data)?
You just need a minor change in your function...
private fun getJSONFromAssets(context: Context): String? {
...
val myUsersJsonFile = context.assets.open("users.json")
...
}
Assuming that your json file is at src/main/assets.
If you need to read a JSON file from the src/main/res/raw folder. You can use:
private fun getJSONFromAssets(context: Context): String? {
...
val myUsersJsonFile = context.resources.openRawResource(R.raw.users)
...
}
As you can see, you need a Context, so you can call from your activity.
getJSONFromAssets(this) // "this" is your activity (or another Context)
Related
I am testing with MockWebServer.
And I need a lot of json files for request and response data.
Hard coded json values seem messy and I want to create json files instead.
So, I created json files in resources(test). And I tried to read file with these methods.
object TestHelper {
fun read(fileName: String): String {
val resource = javaClass.classLoader?.getResource(fileName)
return resource?.readText() ?: ""
}
fun readJson(fileName: String): String {
val byteArray = readBinaryFileFromResources(fileName)
val sb = StringBuilder("")
byteArray.forEach {
println("byte: $it")
sb.append(it as Char)
}
return sb.toString()
}
#Throws(IOException::class)
fun readBinaryFileFromResources(fileName: String): ByteArray {
var inputStream: InputStream? = null
val byteStream = ByteArrayOutputStream()
try {
inputStream = javaClass.classLoader?.getResourceAsStream(fileName)
var nextValue = inputStream?.read() ?: -1
while (nextValue != -1) {
byteStream.write(nextValue)
nextValue = inputStream?.read() ?: -1
}
return byteStream.toByteArray()
} catch (e: Exception) {
println(e.stackTraceToString())
return byteStream.toByteArray()
} finally {
inputStream?.close()
byteStream.close()
}
}
}
None of them seems work. What's the problem with this code?
I've had trouble with this before, and I believe it has to do with getting the correct classLoader from the call site, as well as having resources in the src/test/resources not being accessible properly. I eventually got it to work by passing in the calling test class as a reified type parameter:
inline fun <reified T> loadFileText(
caller: T,
filePath: String
): String =
T::class.java.getResource(filePath)?.readText() ?: throw IllegalArgumentException(
"Could not find file $filePath. Make sure to put it in the correct resources folder for $caller's runtime."
)
For my setup I have a separate shared module :testtools that I use testImplementation to include in my :app's gradle build so they don't get compiled into the production APK. I have my test resources in:
/testtools/src/main/resources/customfolder
And then calling this from a unit test class in :app like so:
class UnitTestClass {
#Test
fun myTest() {
loadFileText(this, "/customfolder/file_name.txt")
}
}
You might have some luck putting your resources straight into /app/src/test/resources/customfolder, I haven't tried in a while.
on Android, using com.google.gson:gson:2.8.5,
when passing a josnString and when the json is large (noticed for example when string length is 669304), it got
com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException:
Unterminated string at line 2902 column 564904 path $.items.result[10].quoted_status.full_text
or different errors:
the json has array of sub json strings, structured like,
{
"items": {
"result": [
{subJsonString...},
{subJsonString...},
...
]
}
}
and the json string is stored in the res/raw/jsonstring.txt, and read in at runtime before give to Gson fro deserialization.
if reduce the sub json elements in the array (i.e. 10 items or less in the array) it works fine, and the individual json elements string all working fine. But when the array has more items it starts to throw.
update: it seems is problem from reading the json string from res/raw
after further look,
the log shows the output json string read from the res/raw/jsonString.txt are corrupted, when there are more items. Here after adding the 11th sub item into the json string array, it shows 000000000... at certain point (the 11th item who encountered issue are just a copy from the 1st item, so the string should be fine)
here is the code how the raw string is read, how it logs it
val jsonStr = getJsonFromFile(context, "res/raw/jsoninraw.txt")
returnedModel = gson.fromJson<T>(jsonStr, dataClassType)
......
fun getJsonFromFile(context: Context?, fileName: String): String? {
val json: String
try {
val inputStream: InputStream = context.getAssets().open(fileName)
val size = inputStream.available()
val buffer = ByteArray(size)
inputStream.use { it.read(buffer) }
json = String(buffer)
} catch (ioException: IOException) {
ioException.printStackTrace()
return null
}
return json
.also {
logString(it)
}
}
fun logString(jsonStr: String) {
val chunkSize = 512
var i = 0
while (i < jsonStr.length) {
Log.e("+++", jsonStr.substring(i, Math.min(jsonStr.length, i + chunkSize)))
i += chunkSize
}
}
what are the better way to deserialize the json string into model?
Found the problem that the ByteArray has 64k limit, so anything beyond is corrupted.
the updated the getJsonFromFile() works.
fun getJsonFromFile(context: Context?, fileName: String?): String? {
val returnString = StringBuilder()
var inputStream: InputStream? = null
var isr: InputStreamReader? = null
var input: BufferedReader? = null
try {
inputStream = if (context == null) {
val cLoader = this.javaClass.classLoader
cLoader.getResourceAsStream(fileName)
} else {
context.getAssets().open(fileName)
}
// either #1:
// returnString.append(inputStream?.readBytes()?.toString(Charsets.UTF_8))
// or #2:
inputStream?.let {
val inputString = inputStream.bufferedReader().use { it.readText() }
returnString.append(inputString)
}
// or #3:
// isr = InputStreamReader(inputStream)
// input = BufferedReader(isr)
// var line: String? = ""
// while (input.readLine().also { line = it } != null) {
// returnString.append(line)
// }
} catch (e: Exception) {
e.printStackTrace()
return null
} finally {
try {
isr?.close()
inputStream?.close()
input?.close()
} catch (e2: Exception) {
e2.message
}
}
return returnString.toString()
}
For storing two-dimensional integer data (matrix) during the life of an application, it takes only one line of code (int myArray[][] = new int[][]) to store this data within the activity, and a relatively simple procedure (like for any other type of variable) using "intent" for transfer between activities.
However, if you want to store exactly the same data outside the runtime of the application, all the solutions that have been offered (for which I'm grateful) involve dozens of code lines.
Does anyone have a simple solution (I mean, as simple a the transfer between activities, or as simple as saving a string variable to SavedPreferences) for saving matrices outside application runtime?
You basically just need to convert the 2D array into a string and then store it in a file. Something along the following lines should work as you require.
private fun writeFileOnInternalStorage(context: Context, sFileName: String, sBody: Array<IntArray>) {
val file = File(context.getFilesDir(), "mydir")
if (!file.exists()) {
file.mkdir()
}
try {
val gpxfile = File(file, sFileName)
val writer = FileWriter(gpxfile)
writer.append(sBody.map {
//Convert the array to string using separators
it.joinToString(separator = ",")
}.joinToString(separator = "|"))
writer.flush()
writer.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun readFileOnInternalStorage(context: Context, sFileName: String): Array<IntArray> {
var result = emptyArray<IntArray>()
val file = File(context.getFilesDir(), "mydir")
if (!file.exists()) {
return result
}
try {
val gpxfile = File(file, sFileName)
val reader = FileReader(gpxfile)
val content = reader.readText()
//Convert the string back into a 2D array using the separators
result = content.split("|").map {
it.split(",").map {
Integer.parseInt(it)
}.toIntArray()
}.toTypedArray()
reader.close()
} catch (e: Exception) {
e.printStackTrace()
}
return result
}
I am new in using gson. I have tried several times to insert a Json Object inside a Json Array in Kotlin. I was able to successfully create the Json Array with Json Object and save it to internal storage. However, I wasn't able to insert Json Object inside it with this code. Any suggestions on how I would be able to achieve this? I've tried to get the file from internal storage and I couldn't fromJson it so I'm not sure if this idea would work. The data in the array are all strings. Here is my code:
fun save(){
var gson = Gson()
val filename = "name"
val file = context?.getFileStreamPath(filename)
if(file == null || !file.exists()){
val array= ArrayList<arraylist>()
array.add(arraylist("1","2","3"))
var json:String=gson.toJson(array).replace("\\n", "\n")
context?.openFileOutput(filename, Context.MODE_PRIVATE).use {
it?.write(json.toByteArray())
}
}
else{
val file = File(context?.filesDir, filename)
val contents = file.readText()
val gson = GsonBuilder().create()
val content = gson.fromJson(contents, arraylist::class.java)
content.add(arraylist("1","2","3"))
var json:String=gson.toJson(content).replace("\\n", "\n")
}
}
I finally fixed this by reading it then saving it to internal storage. This is inside the else{} statement:
val file1 = File(context?.filesDir, filename)
val contents = file1.readText()
val array = gson.fromJson(contents, Array<arraylist>::class.java)
val arrayList = ArrayList(array.toMutableList())
arrayList.add(devices("Finally","Works","Wow"))
val json: String = gson.toJson(arrayList).replace("\\n", "\n")
context?.openFileOutput(filename, Context.MODE_PRIVATE).use {
it?.write(json.toByteArray())
}
hello i'm trying to read json file located at resources folder in androidTest. I'm able to access json file located at resources folder in Test/resources but not in androidTest/resources.
I use following to get the json file in the Test/resources folder.
private fun getJson(path: String): String {
val uri = this.javaClass.classLoader?.getResource(path)
val file = File(uri?.path ?: "")
return String(file.readBytes())
}
Is there a way i can access the file under androidTest/resources ?
private fun getJson(fileName: Int): String {
val inputStream = InstrumentationRegistry.getInstrumentation().targetContext.resources.openRawResource(fileName)
val s = Scanner(inputStream).useDelimiter("\\A")
return if (s.hasNext()) s.next() else ""
}