iam using retrofit 2 with moshi and i have a problem when the optional nested class is broken.
Lets take this as a simple example:
class ClassA(val anyString: String, classB: ClassB?)
class ClassB(val firstString: String, val secondString: String)
I have a class A that has an optional Value from class B.
The Json that is gonna be parsed looks like this:
{
"anystring":"HelloWorld",
"classB":{
"firstString":"IamAFirstString"
//SECOND STRING IS MISSING OR IS NULL
}
}
When i try to parse this json ill get an exception:
com.squareup.moshi.JsonDataException: Non-null value 'secondString' was null at $.classB.secondString
What i want to archive is that i get the ClassA with an null value at classB because that value is optional so its okay when its null. I know that can archive this when i build an adapter and catch the exception but i want to know if theres a general way to archive this behaviour without creating an adapter for every json that i have to parse.
Related
I have an API request to handle that sometimes returns with code 200 and a value of something like
{ "key" : "value" }
But in some cases, API might return with code 204. Handling this is a whole other story, but I end up with an interceptor that replaces a response body with an empty string, so it won't throw NoSuchElementException.
Now I want to handle the result with Jackson in a way, that in a case of 200 I would get an object of class A described like this:
class A(#JsonProperty("key") val value: String?)
But in the case of 204 and empty body response, I would like to get an instance of A with value property equals null.
I've tried adding a custom deserialiser, but I get the following exception even before my deserialiser code is executed:
com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input at [Source: (String)""; line: 1, column: 0]
This is kind of obvious since there's no top-level JSON object in an empty string.
Is there a way I can achieve desired behaviour using Jackson?
I'm not familiar with Jackson but I notice you said
I end up with an interceptor that replaces a response body with an empty string
Can't you just then instead of empty string return "{ \"key\" : null }" instead?
I have an API which its response is kind of dynamic. I mean sometimes it return a Jason object with "token" value, and sometimes it returns with "message" value. For handling this scenario I decided to have both field in my data class like below:
data class response {
val message:String;
val token:String;
}
Now I want to make both fields optional in Kotlin serialization. I mean, I want to tell Kotlin serialization that if you couldn't find token in response JSON it's ok to ignore it.
How can I achieve this?
All Kotlin properties with default values are automatically optional.
All I need to do is this:
data class response {
val message:String="";
val token:String="";
}
I am trying to parse the results of an API call which returns a unique first property.
{
"AlwaysDifferent12345": {
"fixedname1" : "ABC1",
"fixedname2" : "ABC2"
}
}
I am using retrofit2 and jackson/gson and cannot figure out how to cope with dynamic property names within the retrofit2 framework. The following works fine
data class AlwaysDifferentDTO(
#JsonProperty("AlwaysDifferent12345") val alwaysDifferentEntry: AlwaysDifferentEntry
)
I have tried
data class AlwaysDifferentDTO(
#JsonProperty
val response: Map<String, AlwaysDifferentEntry>
)
But this returns errors Can not instantiate value of type... The return value from the API is fixed i.e. map<string, object>.
I have read you can write a deserializer but it looks like I need to deserialize the whole object when all I want to do is just ignore the string associated with the response.
I have read
https://discuss.kotlinlang.org/t/set-dynamic-serializedname-annotation-for-gson-data-class/14758
and several other answers. Given unique properties names are quite common it would be nice to understand how people deal with this when using retrofit2
Thanks
Because the JSON doesn't have a 1-to-1 mapping Jackson can't map it automatically using annotations. You are going to need to make your own Deserializer.
In this tutorial you can learn how to create your own custom Deserializer for Jackson. https://www.baeldung.com/jackson-deserialization
In the tutorial you will see the first line under the deserialize function is
JsonNode node = jp.getCodec().readTree(jp);
using this line you can get the JSON node as a whole and once you have it you can call this function
JsonNode AlwaysDifferent12345Node = node.findParent("fixedname1");
Now that you have that node you can retrieve its value like shown in the rest of the tutorial. Once you have all the values you can return a new instance of the AlwaysDifferentDTO data class.
I am running into some unknown error. This breaks my assumption of null-safety with Kotlin data class and Api responses.
Say, I have a data class say Person:
data class Person(val name: String) {
constructor() : this("")
}
This will generate an object Person with default name value i.e. non-null.
Earlier, When I use a default retrofit client with GsonConverterFactory.create() (added as a converter factory). In default mode, Gson doesn't serialize a null value. But today I found out that field is getting serialized to null.
I verfiy the same in ReflectiveTypeAdapterFactory https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java#L206
Here the instance value is having non-null field but after reading each field (field.read(in, instance);) it is assigning the null value.
I am expecting the null values to be skipped during serialization or is it deserialization?
Edit:
Looks like it is deserializing nulls not serializing null problem.
Reference: https://github.com/google/gson/issues/1148
Let me know if any detail missing or creating confusion.
You have to make name parameter nullable by changing type;
String
to
String?
I have converted the following Swift code:
struct FooModel: Decodable {
public let id: String
public let bars: [[BarModel]]
}
to this Kotlin code:
data class FooModel (val id: String, val bars: List<List<BarModel>>)
The issue I am encountering, is my id is coming in null for the Kotlin code (via gson). Everything else in the Kotlin conversion is working fine and the entire JSON is populating all data classes, except for this tiny piece (the id variable).
I suspect my conversion here is the cause, any ideas?
If the id should be nullable do it like this:
data class FooModel (
val id: String?,
val bars: List<List<BarModel>>
)
The question mark makes this property nullable.
If the JSON you are getting is correct (the id value is there and coming to you as a string), your code should work. It's unclear what could be going wrong here if that's the case.
However, it is worth knowing that there is a big potential "gotcha" with Gson that you should be aware of: it's possible to declare a variable of a data class as non-nullable but still get a null after conversion. This can happen when an expected value is missing from the JSON response. In these cases Gson does not throw an error and I only found out later when I got a crash trying to access the non-nullable variable that should never have made it to me as null. I discovered this is a consequence of Gson using something like Class.newInstance() instead of a regular constructor when it creates these data classes, and then uses reflection to populate the data. More is written about this in another answer here: Why Kotlin data classes can have nulls in non-nullable fields with Gson?
Depending on your use case you might consider this to be a design flaw and a reason to avoid Gson in favor of other JSON serialization libraries. My personal favorite at the moment is Square's Moshi.
You can check if the value type you are getting from server matches with your variable id i.e. String on both the sides. Secondly you can try using SerializedName("id") included in library:
implementation 'com.google.code.gson:gson:2.9.0'