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

@JsonAdapter + JsonDeserializer make JsonSerializer fail #1783

Closed
DoneSpeak opened this issue Sep 15, 2020 · 2 comments
Closed

@JsonAdapter + JsonDeserializer make JsonSerializer fail #1783

DoneSpeak opened this issue Sep 15, 2020 · 2 comments
Labels

Comments

@DoneSpeak
Copy link

DoneSpeak commented Sep 15, 2020

I want to deserialize the string to list so I use @JsonAdapter + JsonDeserializer to solve the problem, but when I try to serialize the List, I get a {}.

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.5</version>
</dependency>
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

import com.google.gson.JsonPrimitive;
import com.google.gson.annotations.JsonAdapter;
import lombok.Data;

public class Temp {

    @Data
    public static class DataValue {
        @JsonAdapter(ListDeserializer.class)
        private List<String> list;
    }

    public static class ListDeserializer implements JsonDeserializer<List<String>> {

        @Override
        public List<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {
            if(json.isJsonNull()) {
                return null;
            }
            if (json.isJsonNull()) {
                return null;
            }
            if (json.isJsonArray()) {
                Iterator<JsonElement> iter = json.getAsJsonArray().iterator();
                final List<String> result = new ArrayList<>();
                iter.forEachRemaining(e -> result.add(e.getAsString()));
                return result;
            }
            if (json.isJsonPrimitive()) {
                JsonPrimitive jsonPrimitive = (JsonPrimitive) json;
                String str = jsonPrimitive.getAsString();
                return Arrays.asList(str.split(","));
            }
            return null;
        }
    }

    public static void main(String[] args) {
        GsonBuilder gsonBuilder = new GsonBuilder();
        // gsonBuilder.setPrettyPrinting();
        Gson gson = gsonBuilder.create();

        String json = "{\"list\":\"a,b,c,d,e,f\"}";
        DataValue dataValue = gson.fromJson(json, DataValue.class);
        // Temp.DataValue(list=[a, b, c, d, e, f])
        System.out.println(dataValue);
        // {"list":{}}
        System.out.println(gson.toJson(dataValue));
    }
}
@Marcono1234
Copy link
Collaborator

Marcono1234 commented Sep 18, 2020

It looks like this describes the same problem as #1326 (though that issue is not as detailed).

The underlying issue is similar to #1028: Because ListDeserializer does not implement the JsonSerializer interface the TreeTypeAdapter (internal class) tries to get the delegate using Gson.getDelegateAdapter(TypeAdapterFactory, TypeToken). This method erroneously thinks that the adapter factory was created using a @JsonAdapter placed on the class (instead of the field which is the case in your code) and therefore skips nearly all built-in adapters, falling back to the reflection based serialization. However not even the backing array field of the list is serialized because the TreeTypeAdapter looked up the adapter for the interface List (instead of the runtime class of the value, i.e. java.util.Arrays.ArrayList) which does not have any fields.

For now a workaround could be to use a TypeAdapterFactory:

@Data
public static class DataValue {
    @JsonAdapter(StringListTypeAdapterFactory.class)
    private List<String> list;
}

/**
 * Custom TypeAdapterFactory for {@code List<String>}.
 * 
 * <p>Only intended for usage with {@link JsonAdapter @JsonAdapter} on fields.
 */
private static class StringListTypeAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        // Cannot use getDelegateAdapter(...) due to https://github.com/google/gson/issues/1028
        // However if this factory is only used with @JsonAdapter on
        // fields then using getAdapter(...) is safe
        @SuppressWarnings("unchecked")
        TypeAdapter<List<String>> delegate = (TypeAdapter<List<String>>) gson.getAdapter(type);
        
        TypeAdapter<List<String>> adapter = new TypeAdapter<List<String>>() {
            @Override
            public List<String> read(JsonReader in) throws IOException {
                JsonToken peeked = in.peek();
                if (peeked == JsonToken.NULL) {
                    return null;
                } else if (peeked == JsonToken.BEGIN_ARRAY) {
                    return delegate.read(in);
                } else if (peeked == JsonToken.STRING) {
                    String str = in.nextString();
                    return Arrays.asList(str.split(","));
                } else {
                    return null;
                }
            }
            
            @Override
            public void write(JsonWriter out, List<String> value) throws IOException {
                delegate.write(out, value);
            }
        };
        
        @SuppressWarnings("unchecked")
        TypeAdapter<T> result = (TypeAdapter<T>) adapter;
        return result;
    }
}

Marcono1234 added a commit to Marcono1234/gson that referenced this issue Sep 19, 2020
@Marcono1234 Marcono1234 added the bug label Aug 3, 2022
@Marcono1234
Copy link
Collaborator

Marcono1234 commented Aug 23, 2023

It appears this has been fixed by #2435; the output is now:

{"list":["a","b","c","d","e","f"]}

(though I omitted the Lombok @Data annotation from your sample code for testing)

I am therefore going to close this issue. But please let us know if you can still reproduce this with the latest Gson code from the main branch, respectively the next upcoming Gson version (2.11.0 probably).

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

No branches or pull requests

2 participants