Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gson deserializes wildcards to LinkedHashMap #1107

Open
egor-n opened this issue Jun 26, 2017 · 5 comments
Open

Gson deserializes wildcards to LinkedHashMap #1107

egor-n opened this issue Jun 26, 2017 · 5 comments

Comments

@egor-n
Copy link
Contributor

egor-n commented Jun 26, 2017

This issue is a successor to #1101.

Models:

// ? extends causes the issue
class BigClass { Map<String, ? extends List<SmallClass>> inBig; }

class SmallClass { String inSmall; }

Json:

{
  "inBig": {
    "key": [
      { "inSmall": "hello" }
    ]
  }
}

Gson call:

SmallClass small = new Gson().fromJson(json, BigClass.class).inBig.get("inSmall").get(0);

This call will fail with a ClassCastException exception –
com.google.gson.internal.LinkedTreeMap cannot be cast to Entry. If we remove ? extends then everything works fine.

inder123 pushed a commit that referenced this issue Sep 22, 2017
…1146)

* fix issue #1107: resolve element type in wildcard collection types

* fix Codacy warnings

* fix Codacy warnings
sebasjm pushed a commit to sebasjm/gson that referenced this issue Mar 11, 2018
…pes (google#1146)

* fix issue google#1107: resolve element type in wildcard collection types

* fix Codacy warnings

* fix Codacy warnings
@kiptoomm
Copy link

was this ever fixed/released? I still see the problem even on v2.8.5

AlexanderKozubets added a commit to techmagic-team/techmagic-hr-android that referenced this issue Feb 24, 2019
@Marcono1234
Copy link
Collaborator

Could you please test if this still occurs with the latest Gson version? I think #1146 should have fixed this. At least the originally provided sample code seems to work, except that the member name seems to be incorrect: fromJson(json, BigClass.class).inBig.get("inSmallkey").get(0)

If you still experience this please provide a small self contained example, and mention which Gson version you are using.

@mhd5uhail
Copy link

mhd5uhail commented Mar 14, 2023

Hi,

Im using Gson 2.10.1. I think the issue is still present. I was trying to write a class that can
parse json array from a json file stored in the android assets folder.

The following is the class for the parser. This takes in a Generic Type.

class AssetJsonParser<T>  {  
    companion object{  
        private const val TAG = "AssetJsonParser"  
  }  
  
    fun getDataAsList(context: Context, filePath : String): List<T> {  
        lateinit var jsonString : String  
        try {  
            jsonString = context.assets.open(filePath).bufferedReader().use {  
 it.readText()  
            }  
  }catch (ioException: IOException){  
            Log.d(TAG, "parse: failed due to : ${ioException.message}" )  
        }  
        val listOfData = object : TypeToken<List<T>>() {}.type  
  return Gson().fromJson(jsonString,listOfData)  
    }  
}

In the test case below, I was expecting the Gson parser to return a list of the generic type (here TypeA or TypeB).

I was expecting this test case to pass but I see that the type returned is actually a
LinkedTreeMap type and not what is expected (TypeA or TypeB). Is it an issue with my
way of using the Gson library or is it same as the issue #1107 mentioned here ?

@Test  
fun typeTest() {  
    // Context of the app under test.  
  val appContext = InstrumentationRegistry.getInstrumentation().targetContext  
  
  val parserTypeA = AssetJsonParser<TypeA>()  
    val parserTypeB = AssetJsonParser<TypeB>()  
  
    val listOfTypeA = parserTypeA.getDataAsList(appContext, "TypeA.json")  
    val listOfTypeB = parserTypeB.getDataAsList(appContext, "TypeB.json")  
  
    assertTrue(listOfTypeA[0] is TypeA)  
    assertTrue(listOfTypeB[0] is TypeB)  
  
}

Additional Details

These are two data classes and the json file contents that were used in the test case shown above

TypeA.kt

data class TypeA (  
    @SerializedName("key")  
    @Expose  
  var key: Int,  
  @SerializedName("value")  
    @Expose  
  var value: String,  
)

TypeB.kt

data class TypeB (  
    @SerializedName("key")  
    @Expose  
  var key: Int,  
  @SerializedName("value")  
    @Expose  
  var value: String,  
  @SerializedName("value")  
    @Expose  
  var desc: String,  
)

Type A. json

[  
  {  
    "key": 1,  
  "value": "one"  
  },  
  {  
    "key": 2,  
  "value": "two"  
  }  
]

TypeB.json

[  
  {  
    "key": 1,  
  "value": "one",  
  "desc": ""  
  },  
  {  
    "key": 2,  
  "value": "two",  
  "desc": ""  
  }  
]

@Marcono1234
Copy link
Collaborator

Marcono1234 commented Mar 14, 2023

@mhdsuhail172, that is a different issue. The problem with your code is that it uses TypeToken<List<T>>() {}. Due to type erasure at runtime your code is actually equivalent to TypeToken<List<Any>>() {}. There is nothing Gson can do to solve this, other than failing fast when it notices such errors (see #1219).

You have to make T or rather TypeToken<T> a property of your class, provided to the constructor, and then use TypeToken.getParameterized​(List.class, elementTypeToken.getType()) to construct your TypeToken<List<T>>. Or you could try to rewrite your code to use Kotlin's reified type parameter; in that case the actual value for T is present at compile time.

@mhd5uhail
Copy link

mhd5uhail commented Mar 15, 2023

@Marcono1234 thank you so much for guiding me to the reified type parameter. This helped solve this . I changed the function to an inline reified function like shown below. Now the type info is present at runtime and its able to deserialize perfectly to TypeA or TypeB.

class AssetJsonParser  {
    val TAG = "AssetJsonParser"

    inline fun <reified T> getDataAsList(context: Context, filePath : String): List<T> {
        lateinit var jsonString : String
        try {
            jsonString = context.assets.open(filePath).bufferedReader().use {
                it.readText()
            }
        }catch (ioException: IOException){
            Log.d(TAG, "parse: failed due to : ${ioException.message}" )
        }

        val listOfData = object : TypeToken<List<T>>()  {}.type

        return Gson().fromJson(jsonString,listOfData)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants