public class ListResult<T> {
private boolean ok;
private String message;
private java.util.List<T> data;
private Paging paging;
}
When i want to deserialize JSON given form RESTful call like this :
Type fooType = new TypeToken<ListResult<T>>() {}.getType();
Gson gson = new Gson();
Object model = gson.fromJson(strResult, fooType);
A get A ListResult where data field is a list of StringMap instead a List of T like defined in ListResult class
Any idea ?
Type fooType = new TypeToken<ListResult<StringMap>>() {}.getType();
Gson gson = new Gson();
ListResult<StringMap> model = gson.fromJson(strResult, fooType);
Gson can't read your mind, or determine a Generic type all on its own ;)
https://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Generic-Types
Just don't use a list, use an array MyObj[] myObjs. You don't need to use the TypeToken way
Related
I have a class :
data class Stam(#SerializedName("blabla") val blabla: String = "")
I want to do gson.fromJson("{\"blabla\":null}", Stam::class.java)
However, it will fail because blabla is not nullable.
I want to make it so if gson failed to deserialize some variable, it will take the default value I give it.
How to achieve that?
I don't think it is possible with GSON, this is one of the reasons why kotlinx.serialization library was created. With this library it is fairly easy:
#Serializable
data class Stam(#SerialName("blabla") val blabla: String = "") //actually, #SerialName may be omitted if it is equal to field name
Json { coerceInputValues = true }.decodeFromString<Stam>("{\"blabla\":null}")
I wouldn't say it is not possible in Gson, but Gson is definitely not the best choice:
Gson has no mention on Kotlin, its runtime and specifics, so one is better to use a more convenient and Kotlin-aware tool. Typical questions here are: how to detect a data class (if it really matters, can be easily done in Kotlin), how to detect non-null parameters and fields in runtime, etc.
Data classes in Kotlin seem to provide a default constructor resolvable by Gson therefore Gson can invoke it (despite it can instantiate classes instances without constructors using unsafe mechanics) delegating to the "full-featured" constructor with the default arguments. The trick here is removing null-valued properties from input JSON so Gson would keep "default-argumented" fields unaffected.
I do Java but I do believe the following code can be converted easily (if you believe Gson is still a right choice):
final class StripNullTypeAdapterFactory
implements TypeAdapterFactory {
// The rule to check whether this type adapter should be applied.
// Externalizing the rule makes it much more flexible.
private final Predicate<? super TypeToken<?>> isClassSupported;
private StripNullTypeAdapterFactory(final Predicate<? super TypeToken<?>> isClassSupported) {
this.isClassSupported = isClassSupported;
}
static TypeAdapterFactory create(final Predicate<? super TypeToken<?>> isClassSupported) {
return new StripNullTypeAdapterFactory(isClassSupported);
}
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !isClassSupported.test(typeToken) ) {
return null;
}
// If the type is supported by the rule, get the type "real" delegate
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, typeToken);
return new StripNullTypeAdapter<>(delegate);
}
private static final class StripNullTypeAdapter<T>
extends TypeAdapter<T> {
private final TypeAdapter<T> delegate;
private StripNullTypeAdapter(final TypeAdapter<T> delegate) {
this.delegate = delegate;
}
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegate.write(out, value);
}
#Override
public T read(final JsonReader in) {
// Another disadvantage in using Gson:
// the null-stripped object must be buffered into memory regardless how big it is.
// So it may generate really big memory footprints.
final JsonObject buffer = JsonParser.parseReader(in).getAsJsonObject();
// Strip null properties from the object
for ( final Iterator<Map.Entry<String, JsonElement>> i = buffer.entrySet().iterator(); i.hasNext(); ) {
final Map.Entry<String, JsonElement> property = i.next();
if ( property.getValue().isJsonNull() ) {
i.remove();
}
}
// Now there is no null values so Gson would only use properties appearing in the buffer
return delegate.fromJsonTree(buffer);
}
}
}
Test:
public final class StripNullTypeAdapterFactoryTest {
private static final Collection<Class<?>> supportedClasses = ImmutableSet.of(Stam.class);
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
// I don't know how easy detecting data classes and non-null parameters is
// but since the rule is externalized, let's just lookup it
// in the "known classes" registry
.registerTypeAdapterFactory(StripNullTypeAdapterFactory.create(typeToken -> supportedClasses.contains(typeToken.getRawType())))
.create();
#Test
public void test() {
final Stam stam = gson.fromJson("{\"blabla\":null}", Stam.class);
// The test is "green" since
Assertions.assertEquals("", stam.getBlabla());
}
}
I still think Gson is not the best choice here.
I am refactoring and adding to the API communication of an app. I'd like to get to this usage for my "json data objects". Instantiate with either the properties directly or from a json string.
userFromParams = User("user#example.com", "otherproperty")
userFromString = User.fromJson(someJsonString)!!
// userIWantFromString = User(someJsonString)
Getting userFromParams to serialize to JSON was not a problem. Just adding a toJson() function takes care of that.
data class User(email: String, other_property: String) {
fun toJson(): String {
return Moshi.Builder().build()
.adapter(User::class.java)
.toJson(this)
}
companion object {
fun fromJson(json: String): User? {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
return moshi.adapter(User::class.java).fromJson(json)
}
}
}
It is "fromJson" that I would like to get rid of ...because... I want to and I can't figure out how. The above class works (give or take wether to allow an optional object to be returned or not and so on) but it just bugs me that I get stuck trying to get to this nice clean overloaded initialization.
It does not strictly have to be a data class either, but it does seem appropriate here.
You can't really do that in any performant way. Any constructor invocation will instantiate a new object, but since Moshi handles object creation internally, you'll have two instances...
If you really REALLY want it though, you can try something like:
class User {
val email: String
val other_property: String
constructor(email: String, other_property: String) {
this.email = email
this.other_property = other_property
}
constructor(json: String) {
val delegate = Moshi.Builder().build().adapter(User::class.java).fromJson(json)
this.email = delegate.email
this.other_property = delegate.other_property
}
fun toJson(): String {
return Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
.adapter(User::class.java)
.toJson(this)
}
}
I have a simple class used for JSON serialization. For this purpose, the external interface uses Strings, but the internal representation is different.
public class TheClass {
private final ComplexInfo info;
public TheClass(String info) {
this.info = new ComplexInfo(info);
}
public String getInfo() {
return this.info.getAsString();
}
// ...more stuff which uses the ComplexInfo...
}
I have this working in Kotlin (not sure if there's a better way). But the non-val/var constructor prevents me from using data.
/*data*/ class TheClass(info: String) {
private val _info = ComplexInfo(info)
val info: String
get() = _info.getAsString()
// ...more stuff which uses the ComplexInfo...
}
How do I get this working as a data class?
You can use a combination of a private ComplexInfo property declared in the primary constructor and a secondary constructor that accepts a String.
Optionally, make the primary constructor private.
Example:
data class TheClass private constructor(private val complexInfo: ComplexInfo) {
constructor(infoString: String) : this(ComplexInfo(infoString))
val info: String get() = complexInfo.getAsString()
}
Note that it's the complexInfo property that is used in the data class generated members implementations.
I have this bean
class Toto
{
private final String fuu;
private final String bar;
Private List<Dog> dogs;
}
class Dog {
private String name;
private String race;
}
I want to convert this bean into a Map<> using:
ObjectMapper serializer = new ObjectMapper();
serializer.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return serializer.convertValue(entry, Map.class);
This code result is that Dog objects are also converted into a Map.
I want a solution so that in the final map i would have:
fuu=fuu_value_as_String
bar=bar_value_as_String
dogs=[ObjectDog1, ObjectDog2] // i do not want the Object to be
converted (because i have an instanceof check later in the code)
Thanks
public class Main {
static class Account {
private Long id;
private String name;
private Book book;
public Account(Long id, String name, Book book) {
this.id = id;
this.name = name;
this.book = book;
}
public String getName() {
return name;
}
}
public static void main(String[] args) {
List<Account> data1 = new ArrayList<>();
data1.add(new Account(1L,"name",null));
List<String> collect = data1.stream().map(account -> account.getName()).collect(Collectors.toList());
System.out.println(collect);
}
}
In the above code I am trying to convert the following line
List<String> collect = data1.stream().map(account -> account.getName()).collect(Collectors.toList());
into kotlin code. Kotlin online editor gives me the following code
val collect = data1.stream().map({ account-> account.getName() }).collect(Collectors.toList())
println(collect)
which gives compilation error when i try to run it.
how to fix this???
or what is the kotlin way to get list of string from list of Account Object
Kotlin collections don't have a stream() method.
As mentioned in https://youtrack.jetbrains.com/issue/KT-5175,
you can use
(data1 as java.util.Collection<Account>).stream()...
or you can use one of the native Kotlin alternatives that don't use streams, listed in the answers to this question:
val list = data1.map { it.name }
As #JBNizet says, don't use streams at all, if you are converting to Kotlin then convert all the way:
List<String> collect = data1.stream().map(account -> account.getName()).collect(Collectors.toList());
to
val collect = data1.map { it.name } // already is a list, and use property `name`
and in other cases you will find that other collection types can become lists simply with toList() or to a set as toSet() and so on. And everything in Streams has an equivalent in Kotlin runtime already.
There is no need for Java 8 Streams at all with Kotlin, they are more verbose and add no value.
For more replacements to avoid Streams, read: What Java 8 Stream.collect equivalents are available in the standard Kotlin library?
You should read the following as well:
Kotlin API reference for kotlin.collections
Kotlin API reference for kotlin.sequences
And maybe this is a duplicate of: How can I call collect(Collectors.toList()) on a Java 8 Stream in Kotlin?