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

Can't deserialized EnumMap with override toString() #2131

Closed
2 tasks done
Evyde opened this issue Jun 9, 2022 · 8 comments · Fixed by #2138
Closed
2 tasks done

Can't deserialized EnumMap with override toString() #2131

Evyde opened this issue Jun 9, 2022 · 8 comments · Fixed by #2138
Labels

Comments

@Evyde
Copy link

Evyde commented Jun 9, 2022

Gson version

2.9.0

Java / Android version

JDK 17.0.2

Used tools

  • Maven; version: 3.8.5

Description

When serialize EnumMap to Json, it has strange behavior that calls toString() instead of name() of enum key unlike when serialize the normal enum type do.

Expected behavior

{"single_fruit":"APPLE","multi_fruit":{"APPLE":3,"BANANA":2}}

Actual behavior

{"single_fruit":"APPLE","multi_fruit":{"Golden Apple":3,"Green Banana":2}}

Reproduction steps

See this simple program below.

import com.google.gson.Gson;

import java.util.EnumMap;
import java.util.ResourceBundle;


public class Main {

    public enum Fruit {
        APPLE,
        BANANA;
        private final ResourceBundle bundle = ResourceBundle.getBundle("Main");

        @Override
        public String toString() {
            return bundle.getString(this.name());
        }
    }
    public static class FruitData {
        public Fruit single_fruit;
        public EnumMap<Fruit, Integer> multi_fruit;
    }
    public static void main(String[] args) {
        Gson jsonParser = new Gson();
        FruitData f = new FruitData();
        f.single_fruit = Fruit.APPLE;
        f.multi_fruit = new EnumMap<>(Fruit.class);
        f.multi_fruit.put(Fruit.APPLE, 3);
        f.multi_fruit.put(Fruit.BANANA, 2);

        System.out.println(jsonParser.toJson(f));

        // it should be successful
        // System.out.println(jsonParser.fromJson(jsonParser.toJson(f), FruitData.class));
    }
}

Main.properties

APPLE=Golden Apple
BANANA=Green Banana

Exception stack trace

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "key" is null
	at java.base/java.util.EnumMap.typeCheck(EnumMap.java:736)
	at java.base/java.util.EnumMap.put(EnumMap.java:264)
	at java.base/java.util.EnumMap.put(EnumMap.java:78)
	at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:188)
	at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:130)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:221)
	at com.google.gson.Gson.fromJson(Gson.java:991)
	at com.google.gson.Gson.fromJson(Gson.java:956)
	at com.google.gson.Gson.fromJson(Gson.java:905)
	at com.google.gson.Gson.fromJson(Gson.java:876)
	at Main.main(Main.java:35)

TODO List

@Evyde Evyde added the bug label Jun 9, 2022
@Marcono1234
Copy link
Collaborator

Relates to / duplicates #127, #1722, #1920

(Note that I am not a member of this project.)

@Evyde
Copy link
Author

Evyde commented Jun 11, 2022

Relates to / duplicates #127, #1722, #1920

(Note that I am not a member of this project.)

Thanks for mentioning these issues.
I've checked the source code before but not certitude that problem is caused by

private String keyToString(JsonElement keyElement) {

Now I'm certainty this caused this problem: It should process EnumMap independently.
After checking these issues, I also find a temporary method to solve this issue(Also use code above for demonstration):

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.EnumMap;
import java.util.ResourceBundle;


public class Main {

    public enum Fruit {
        @SerializedName("APPLE")
        APPLE,
        @SerializedName("BANANA")
        BANANA;
        private final ResourceBundle bundle = ResourceBundle.getBundle("Main");

        @Override
        public String toString() {
            return bundle.getString(this.name());
        }
    }
    public static class FruitData {
        public Fruit single_fruit;
        public EnumMap<Fruit, Integer> multi_fruit;
    }
    public static void main(String[] args) {
        Gson jsonParser = new GsonBuilder().enableComplexMapKeySerialization().create();
        FruitData f = new FruitData();
        f.single_fruit = Fruit.APPLE;
        f.multi_fruit = new EnumMap<>(Fruit.class);
        f.multi_fruit.put(Fruit.APPLE, 3);
        f.multi_fruit.put(Fruit.BANANA, 2);

        System.out.println(jsonParser.toJson(f));

        // it should be successful now
        System.out.println(jsonParser.fromJson(jsonParser.toJson(f), FruitData.class));
    }
}

Diff from code above:

import com.google.gson.GsonBuilder;
...
        @SerializedName("APPLE")
        APPLE,
        @SerializedName("BANANA")
        BANANA;
...
        Gson jsonParser = new GsonBuilder().enableComplexMapKeySerialization().create();
...

But this issue should be maintain open as this bug still exists after those issues & PRs.

@Marcono1234
Copy link
Collaborator

I've checked the source code before but not certitude that problem is caused by THIS. Now I'm certainty this caused this problem

I think that is unrelated to your original issue since that code is used for 'complex map key serialization' (which you had not enabled initially). For non-'complex map key serialization' (the default) the problem is the usage of String.valueOf here:

Your workaround to use enableComplexMapKeySerialization() is a good idea. You can probably even use that without having to use @SerializedName on the enum constants.

@Evyde
Copy link
Author

Evyde commented Jun 12, 2022

I've checked the source code before but not certitude that problem is caused by

private String keyToString(JsonElement keyElement) {

Now I'm certainty this caused this problem

I think that is unrelated to your original issue since that code is used for 'complex map key serialization' (which you had not enabled initially). For non-'complex map key serialization' (the default) the problem is the usage of String.valueOf here:

Your workaround to use enableComplexMapKeySerialization() is a good idea. You can probably even use that without having to use @SerializedName on the enum constants.

Got it, thanks.
Does this or GsonBuilder configuration things need to be mentioned in the User Guide like a table of contents or pointer to JavaDoc?

@Marcono1234
Copy link
Collaborator

Does this or GsonBuilder configuration things need to be mentioned in the User Guide like a table of contents or pointer to JavaDoc?

Yes might indeed be good to mention Map serialization in general, including enableComplexMapKeySerialization() and the toString() issue, in the User Guide, maybe below "Collection Examples".

What do you think? If you want I can create a pull request with an initial draft for this.

@Evyde
Copy link
Author

Evyde commented Jun 15, 2022

Yes might indeed be good to mention Map serialization in general, including enableComplexMapKeySerialization() and the toString() issue, in the User Guide, maybe below "Collection Examples".

What do you think? If you want I can create a pull request with an initial draft for this.

I think it is no necessary to open an independent PR to update README especially this only needs just a single sentence such as:

If you are using a Map with complex key such as EnumMap, just call enableComplexMapKeySerialization() in GsonBuilder to have your complex key serialized properly as referenced at JavaDoc.

, you can do this in your next feature/fix PR.

@Marcono1234
Copy link
Collaborator

Have created #2138 now which also documents Map serialization and deserialization in general. You can see the rendered version here: https://github.com/Marcono1234/gson/blob/marcono1234/map-serialization-user-guide/UserGuide.md#TOC-Maps-Examples

I made this a separate PR and did not include it in any other PR to make it easier for the maintainers to review.

@Evyde
Copy link
Author

Evyde commented Jun 27, 2022

Have created #2138 now which also documents Map serialization and deserialization in general. You can see the rendered version here: https://github.com/Marcono1234/gson/blob/marcono1234/map-serialization-user-guide/UserGuide.md#TOC-Maps-Examples

I made this a separate PR and did not include it in any other PR to make it easier for the maintainers to review.

I reviewed this changed README and was extremely satisfied with it. Thanks for your working.
This issue will be closed when this PR been merged.

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

Successfully merging a pull request may close this issue.

2 participants