gson: Enum serializes to null when nothing matches (instead of throwing IOException)
> What steps will reproduce the problem?
1. Implement a POJO that has an enum
2. Attempt to deserialize the POJO using a bad string for the enum's value
3. See that the enum's value is set to null
> What is the expected output? What do you see instead?
I expect to see an exception (probably IOException?), but instead the value is
initialized to null.
> What version of the product are you using? On what operating system?
GSON 2.3, Android 4.4.2
> Please provide any additional information below.
Here's the relevant part of TypeAdapters.EnumTypeAdapter
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
return nameToConstant.get(in.nextString());
}
`nameToConstant.get()` gives null when nothing is found.
Original issue reported on code.google.com by me@benjam.info
on 12 Nov 2014 at 2:17
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Reactions: 4
- Comments: 16
How about something like this:
@SerializedName(defaultValue = TrafficLight.RED) public enum TrafficLight { RED, ORANGE, GREEN }
// defaultValue will also allowed to be null // if this annotation is not present, enum maintains current behavior.
Comments @JakeWharton @swankjesse @joel-leitch?
You can always install your custom type adapter for your enum that enforces this. Gson isn’t going to support this, sorry.
Trying to understand. But how would this cause backward compatibility issues? The case I can think of were there might be a problem is when json data (newer version) containing a new enum field, would be read by a java implementation (old version) using gson. In that case this will throw an exception and not be compatible. But from the java point of view. I would call this not being forward compatible. The question is if that should be a problem. Because one can not be expected to be prepared for the future. So that should not be a limitation to fix this. Regarding ignoring additional field, input. I would consider this more of a change of input type then an additional field. For changed fields it would also throw exceptions If a field name changes. Also what if the type changes in the json data. From String to double that would cause an exception. But what if the type changed from enum to string that should cause an exception if it gets an new value… but it doesn’t it will simply return null for all the unknown enum fields. It also doesn’t ignore the input in the case of a new/wrong enum value. It just reads it with a different value, the value null. It can also be seen different from for example UUID reading. That would throw an IllegalArgumentException in case the UUID is incorrect. If it had the same behavior as the enum parsing it should then return null. One could argue that if UUID parsing could be a problem no UUID should be used. The same can be argued for enum field. If it might be a problem with new enum values. One should probably read strings and parse them in code, not let the gson library handle it. In general the gson library should not make the decision to interpret the input different then expected, but let the user of the library handle it and thus throw an IllegalArgumentException in this case.
Throwing an exception will cause backward compatibility issues. If you introduce a new enum value, you dont want all existing clients to break.
Even though this is relatively old stuff, in case somebody is looking for a solution for checking the missing enum values in a general way, a type adapter factory can be created to handle this:
Too bad that there are no option to decide to throw an exception in that case. If someone add an extra enum value, there won’t be an issue with the existing json which don’t use those value.
@bric3, that looks like a good workaround for specifying a default fallback value! But I think you can simplify / optimize the
read
function slightly:Also, the following line looks wrong:
Your current code just happens to work because the documentation says if the field is static (which is the case for enum constants) the argument passed as
obj
(for your code that isenumValue
) is ignored. Maybe you could also move thisit.get
call outsidesingle { ... }
since it does not have to be re-evaluated for every enum constant.As small side note: Because your
EnumWithUnknownValueTypeAdapterFactory
is stateless, you could probably use a object declaration for it to make it a singleton.Create this remapping type adapter factory when no value matches, but keep
"null"
ornull
asnull
.Edited with suggestions from @Marcono1234.
test
Here another example how to apply a default value instead of null. (Kotlin-Code)
I agree it would be great to make the behavior configurable - both globally and on enum level (via annotation).
On enum level there could be 3 options:
null
The problem is that the adapter makes no difference between an input value: null and a invalid enum value (for example a typo in the enum value). Both return null. While the first is valid, the second is invalid. Thus the adapter returns the same kind of data for both valid and invalid input. It’s not possible to know if the input was valid or invalid without performing an extra additional check if the input was null in case the result is null. I don’t think that is correct behavior and should not be solved by writing a custom adapter. Shouldn’t it throw an IllegalArgumentException when an invalid enum value is given?
Original comment by
inder123
on 12 Nov 2014 at 10:16