Search code examples
androidjsonkotlinalgoliainstantsearch

How would I go about de-serializing a list of objects using Kotlin's serialization library?


I've been running into the following exception at runtime with the debugger trying to de-serialize data from my Algolia index for my Kotlin Android recipe app I am trying to create by using the Kotlinx.Serialization library. The app compiles and runs fine, but no results show on the UI.

kotlinx.serialization.json.JsonDecodingException: Unexpected JSON token at offset -1: Failed to parse 'int'.
 JSON input: {"amount":1.5,"name":"green beans","original":"1.5 pounds of green beans","unit":"pounds","unitLong":"pounds","unitShort":"lbs"}

Now from the looks this exception, It looks like the de-serializer is getting confused try to de-serialize my Ingredients data class. How would I go about de-serializing it?.

Example JSON data that is being sent over.

{
  "cuisine": "European",
  "diet": "Vegetarian",
  "difficulty": 2,
  "dishType": "Dinner",
  "duration": 30,
  "durationUnit": "minutes",
  "image": "https://c.recipeland.com/images/r/1396/12f9fc271d8f1bfac5f6_550.jpg",
  "ingredients": [
    {
      "amount": 1.5,
      "name": "green beans",
      "original": "1.5 pounds of green beans",
      "unit": "pounds",
      "unitLong": "pounds",
      "unitShort": "lbs"
    },
    {
      "amount": 1,
      "name": "onion",
      "original": "1.5 medium onion",
      "unit": "medium",
      "unitLong": "medium",
      "unitShort": "med"
    },
    {
      "amount": 2,
      "name": "garlic",
      "original": "2 teaspoons of garlic",
      "unit": "teaspoons",
      "unitLong": "teaspoons",
      "unitShort": "tsps"
    },
    {
      "amount": 1,
      "name": "olive oil",
      "original": "1 teaspoon olive oil",
      "unit": "teaspoon",
      "unitLong": "teaspoon",
      "unitShort": "tsps"
    },
    {
      "amount": 1,
      "name": "mushrooms",
      "original": "1 cup mushrooms",
      "unit": "cup",
      "unitLong": "cup",
      "unitShort": "cup"
    },
    {
      "amount": 1,
      "name": "cherry tomatoes",
      "original": "1 cup cherry tomatoes",
      "unit": "cup",
      "unitLong": "cup",
      "unitShort": "cup"
    }
  ],
  "name": "Green Beans with Mushrooms and Cherry Tomatoes",
  "preparation": [
    "Steam green beans until tender.",
    "Drain and set aside. Sauté onion and garlic in a medium skillet coated with olive oil, until tender. About 2 to 3 minutes.",
    "Add mushrooms and sauté until tender. Stir in green beans and tomotoes until heated."
  ],
  "yield": 4,
  "objectID": "0"
}

I have my data classes for a recipe set up as the following:

Recipe.kt

@IgnoreExtraProperties
@Serializable
data class Recipe(
    var difficulty: Int = 0,
    var dishType: String? = null,
    var duration: Int = 0,
    var durationUnit: String? = null,
    var image: String? = null,
    var diet: String? = null,
    var cuisine: String? = null,
    var name: String? = null,
    var ingredients: List<Ingredient> = emptyList(),
    var preparation: List<String> = emptyList(),
    var yield: Int = 0
    ) {

Ingredient.kt

@Serializable
data class Ingredient(
    var amount: Int = 0,
    var name: String? = null,
    var original: String? = null, // Original text of the ingredient
    var unit: String? = null,
    var unitLong: String? = null,
    var unitShort: String? = null
)

This block of code I got from Algolia's getting started guide for InstantSearch Android that de-serializes the data from the index.

    private val datasourceFactory = SearcherSingleIndexDataSource.Factory(searcher) { hit ->
        hit.deserialize(Recipe.serializer()) // Problem line I assume
    }

    val pagedListConfig = PagedList.Config.Builder().setPageSize(50).build()
    val recipes: LiveData<PagedList<Recipe>> =
        LivePagedListBuilder(datasourceFactory, pagedListConfig).build()
    val searchBox =
        SearchBoxConnectorPagedList(searcher, listOf(recipes))

I've tried to manually create the object by using the following code, but I run into issues when trying to create the list of ingredients.

    val dataSourceFactory = SearcherSingleIndexDataSource.Factory(searcher) { hit ->
        Recipe(
            hit.json.getPrimitive("difficulty").content.toInt(),
            hit.json.getPrimitive("dishType").content,
            hit.json.getPrimitive("duration").content.toInt(),
            hit.json.getPrimitive("durationUnit").content,
            hit.json.getPrimitive("image").content,
            hit.json.getPrimitive("diet").content,
            hit.json.getPrimitive("cuisine").content,
            hit.json.getPrimitive("name").content,
            listOf(
                Ingredient(
                    hit.json.getPrimitive("amount").content.toInt(),
                    hit.json.getPrimitive("name").content,
                    hit.json.getPrimitive("original").content,
                    hit.json.getPrimitive("unit").content,
                    hit.json.getPrimitive("unitLong").content,
                    hit.json.getPrimitive("unitShort").content
                )
            ),
            hit.json.getArray("preparation").content.map { prep -> prep.content },
            hit.json.getPrimitive("yield").content.toInt()
        )
    }

I'm not 100% sure if I'm properly creating the preparation property member correctly as well as the whole creating the list of ingredients has side-tracked me. Any help would be greatly be appreciated and I apologize for my first post on here being a long one. I've been going at this for a couple of days already and I'm stumped as to what to do next.


Solution

  • As you can see this line:

    kotlinx.serialization.json.JsonDecodingException: Unexpected JSON token at offset -1: Failed to parse 'int'.
    

    Here JsonDecodingException exception occur that's why it is not giving proper response. You must check your all data classes are same variable which is in JSON Object.

    Here I found 1 issue in your data class, First check this JSON Reposne:

    "amount": 1.5
    

    and now check your data class, which has var amount: Int = 0

    @Serializable
    data class Ingredient(
        var amount: Int = 0,
        var name: String? = null,
        var original: String? = null, // Original text of the ingredient
        var unit: String? = null,
        var unitLong: String? = null,
        var unitShort: String? = null
    )
    

    Here JSON Object is in Float and you are storing in it Int, which may cause exception. Make sure all values in data class are proper.

    Or for work around you just make String all variable in data class to check all response show proper, than after just convert them to Int, Float according to your requirements.