0

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?

flaxel
  • 4,173
  • 4
  • 17
  • 30
Lena Bru
  • 13,521
  • 11
  • 61
  • 126
  • have you tried this solution? https://stackoverflow.com/questions/30216317/setting-default-value-to-a-variable-when-deserializing-using-gson – nt95 Oct 15 '20 at 10:40
  • @nt95 I don't think that the solution presented there is acceptable in kotlin, you would need to declare the val as nullable `String?`. What I would attempt to do (not sure if it will work) is to have `blabla` as `String`, but create a nullable setter, and keep the default there, the getter (don't need to generate it) and val itself will still be non-null. As for achieving that in pure Gson, no idea, I use another library for json objects. – AlexT Oct 15 '20 at 10:53
  • Can you do the same and unescape the quotes? – cutiko Oct 15 '20 at 12:00

2 Answers2

0

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}")
0

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.

  • thank you for the elaborate answer, but it still does not do what i need. I need it to take the default value presented if deserialization failed, not if it was null – Lena Bru Oct 18 '20 at 15:01
  • meaning when the resulting content of the property is null, but the property itself was not nullable – Lena Bru Oct 18 '20 at 15:15
  • @LenaBru Your whole question is mainly built around the idea of possible nulls where they are not permitted (and that's a common thing in Kotlin), When you're referring some another state with "deserialization failed" -- what do you actually mean except of the nullness problem? – terrorrussia-keeps-killing Oct 18 '20 at 18:37