How can I ignore unknown enum values during json deserialization?
You can solve this problem with a custom JsonConverter
. Here is one I put together using a few pieces from the StringEnumConverter
class that comes from Json.Net. It should give you the flexibility to handle things whatever way you decide. Here's how it works:
- If the value found in the JSON matches the enum (either as a string or an integer), that value is used. (If the value is integer and there are multiple possible matches, the first of those is used.)
- Otherwise if the enum type is nullable, then the value is set to null.
- Otherwise if the enum has a value called "Unknown", then that value is used.
- Otherwise the first value of the enum is used.
Here is the code. Feel free to change it to meet your needs.
class TolerantEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
Type type = IsNullableType(objectType) ? Nullable.GetUnderlyingType(objectType) : objectType;
return type.IsEnum;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
bool isNullable = IsNullableType(objectType);
Type enumType = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType;
string[] names = Enum.GetNames(enumType);
if (reader.TokenType == JsonToken.String)
{
string enumText = reader.Value.ToString();
if (!string.IsNullOrEmpty(enumText))
{
string match = names
.Where(n => string.Equals(n, enumText, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
if (match != null)
{
return Enum.Parse(enumType, match);
}
}
}
else if (reader.TokenType == JsonToken.Integer)
{
int enumVal = Convert.ToInt32(reader.Value);
int[] values = (int[])Enum.GetValues(enumType);
if (values.Contains(enumVal))
{
return Enum.Parse(enumType, enumVal.ToString());
}
}
if (!isNullable)
{
string defaultName = names
.Where(n => string.Equals(n, "Unknown", StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
if (defaultName == null)
{
defaultName = names.First();
}
return Enum.Parse(enumType, defaultName);
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
private bool IsNullableType(Type t)
{
return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
}
Here is a demo which puts it the converter through its paces using a couple of different enums (one has an "Unknown" value, and the other does not):
[JsonConverter(typeof(TolerantEnumConverter))]
enum Status
{
Ready = 1,
Set = 2,
Go = 3
}
[JsonConverter(typeof(TolerantEnumConverter))]
enum Color
{
Red = 1,
Yellow = 2,
Green = 3,
Unknown = 99
}
class Foo
{
public Status NonNullableStatusWithValidStringValue { get; set; }
public Status NonNullableStatusWithValidIntValue { get; set; }
public Status NonNullableStatusWithInvalidStringValue { get; set; }
public Status NonNullableStatusWithInvalidIntValue { get; set; }
public Status NonNullableStatusWithNullValue { get; set; }
public Status? NullableStatusWithValidStringValue { get; set; }
public Status? NullableStatusWithValidIntValue { get; set; }
public Status? NullableStatusWithInvalidStringValue { get; set; }
public Status? NullableStatusWithInvalidIntValue { get; set; }
public Status? NullableStatusWithNullValue { get; set; }
public Color NonNullableColorWithValidStringValue { get; set; }
public Color NonNullableColorWithValidIntValue { get; set; }
public Color NonNullableColorWithInvalidStringValue { get; set; }
public Color NonNullableColorWithInvalidIntValue { get; set; }
public Color NonNullableColorWithNullValue { get; set; }
public Color? NullableColorWithValidStringValue { get; set; }
public Color? NullableColorWithValidIntValue { get; set; }
public Color? NullableColorWithInvalidStringValue { get; set; }
public Color? NullableColorWithInvalidIntValue { get; set; }
public Color? NullableColorWithNullValue { get; set; }
}
class Program
{
static void Main(string[] args)
{
string json = @"
{
""NonNullableStatusWithValidStringValue"" : ""Set"",
""NonNullableStatusWithValidIntValue"" : 2,
""NonNullableStatusWithInvalidStringValue"" : ""Blah"",
""NonNullableStatusWithInvalidIntValue"" : 9,
""NonNullableStatusWithNullValue"" : null,
""NullableStatusWithValidStringValue"" : ""Go"",
""NullableStatusWithValidIntValue"" : 3,
""NullableStatusWithNullValue"" : null,
""NullableStatusWithInvalidStringValue"" : ""Blah"",
""NullableStatusWithInvalidIntValue"" : 9,
""NonNullableColorWithValidStringValue"" : ""Green"",
""NonNullableColorWithValidIntValue"" : 3,
""NonNullableColorWithInvalidStringValue"" : ""Blah"",
""NonNullableColorWithInvalidIntValue"" : 0,
""NonNullableColorWithNullValue"" : null,
""NullableColorWithValidStringValue"" : ""Yellow"",
""NullableColorWithValidIntValue"" : 2,
""NullableColorWithNullValue"" : null,
""NullableColorWithInvalidStringValue"" : ""Blah"",
""NullableColorWithInvalidIntValue"" : 0,
}";
Foo foo = JsonConvert.DeserializeObject<Foo>(json);
foreach (PropertyInfo prop in typeof(Foo).GetProperties())
{
object val = prop.GetValue(foo, null);
Console.WriteLine(prop.Name + ": " +
(val == null ? "(null)" : val.ToString()));
}
}
}
Output:
NonNullableStatusWithValidStringValue: Set
NonNullableStatusWithValidIntValue: Set
NonNullableStatusWithInvalidStringValue: Ready
NonNullableStatusWithInvalidIntValue: Ready
NonNullableStatusWithNullValue: Ready
NullableStatusWithValidStringValue: Go
NullableStatusWithValidIntValue: Go
NullableStatusWithInvalidStringValue: (null)
NullableStatusWithInvalidIntValue: (null)
NullableStatusWithNullValue: (null)
NonNullableColorWithValidStringValue: Green
NonNullableColorWithValidIntValue: Green
NonNullableColorWithInvalidStringValue: Unknown
NonNullableColorWithInvalidIntValue: Unknown
NonNullableColorWithNullValue: Unknown
NullableColorWithValidStringValue: Yellow
NullableColorWithValidIntValue: Yellow
NullableColorWithInvalidStringValue: (null)
NullableColorWithInvalidIntValue: (null)
NullableColorWithNullValue: (null)
Ignore JSON deserialization if enum map key is null or unknown
You need to enable READ_UNKNOWN_ENUM_VALUES_AS_NULL
which is disabled by default on your ObjectMapper
:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
When you do this, the result Map
will contain one entry with key equals to null
and value set to JSON
object related with one of unknown fields. If it is not acceptable you need to write custom deserialiser or remove null
key after deserialisation process.
To solve problem with null
key in Map
you can also use EnumMap
in your POJO
:
private EnumMap<MyEnum, MyPojo> foo;
where null
keys are not permitted and will be skipped.
See also:
- Jackson deserializing into Map with an Enum Key, POJO Value
How to Serialize an Enum in .Net when it is not recognized
Do you want to access the invalid value you get sent in? If you don't care, you can look at the other answer linked in the comments. Here I assume that you want to see the weird value of ActionType, for logging or similar.
If the string you get is unknown, it can not be represented as an enum. You have to store the string somewhere.
The simplest solution seems to serialize/deserialize the string value directly, and provide a getter/setter that handles the enum. You can either use a nullable enum, or have a "special" value of UNKNOWN or something like that, when the string value is not a valid enum value.
public class DataRequest
{
[JsonProperty("Id")]
public string id { get; set; }
[JsonProperty("Action")]
public string RawActionValue { get; set; }
[JsonIgnore]
public ActionType? Action
{
get {
if(Enum.TryParse<ActionType>(RawActionValue, out var res)) {
return res;
} else {
return null;
}
}
set {
RawActionValue = value?.ToString();
}
}
}
Jackson: ignore unknown enum values
I did not manage to reproduce your problem with jackson 2.5.1
This is my ACE class
public class ACE {
private final Set<Right> accessrights = EnumSet.noneOf(Right.class);
private final Set<Inheritance> inherit_flags
= EnumSet.noneOf(Inheritance.class);
public static enum Right {
traverse, list, dir_read_attr, dir_read_ext_attr, add_file, add_subdir,
dir_write_attr, dir_write_ext_attr, delete_child, std_delete,
std_read_dac, std_write_dac, std_write_owner,
dir_gen_all, dir_gen_execute, dir_gen_read, dir_gen_write, modify;
}
public static enum Inheritance {
object_inherit, container_inherit, no_prop_inherit, inherit_only,
inherited_ace;
}
public Set<Right> getAccessrights() {
return accessrights;
}
public Set<Inheritance> getInherit_flags() {
return inherit_flags;
}
}
this is my test case:
package test;
import test.ACE.Inheritance;
import test.ACE.Right;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestJacksonEnum {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ACE ace= new ACE();
ace.getAccessrights().add(Right.add_file);
ace.getAccessrights().add(Right.add_subdir);
ace.getInherit_flags().add(Inheritance.container_inherit);
ace.getInherit_flags().add(Inheritance.inherit_only);
ObjectMapper mapper=new ObjectMapper();
mapper.configure(
DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
System.out.println(mapper.writeValueAsString(ace));
String serializedFormREST="{\"accessrights\":[\"std_synchronize\",\"add_subdir\"],\"inherit_flags\":[\"container_inherit\",\"inherit_only\"]}";
ACE deserializedAce= mapper.readValue(serializedFormREST, ACE.class);
System.out.println(mapper.writeValueAsString(deserializedAce));
}
}
The deserializedAce object is read correctly and the set does not contain std_synchronize but it contains a null. Am I missing something?
Make Spring Boot JSON enum deserialization strict, so it does not silently convert invalid values into null
That feature should be disabled by default.
But if you want to set it explicitly you can do it like this:
in your properties:
spring.jackson.deserialization.read-unknown-enum-values-as-null=false
or as an alternative in a configuration class (actually any bean would work, just make sure it happens early):
@Autowired
public void configureJackson(ObjectMapper objectMapper) {
objectMapper.disable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
}
Because it should actually be like this by default, I am wondering why it is not for you. Do you enable it somewhere? Which Spring Boot version are you using?
Related Topics
Call Signalr Core Hub Method from Controller
Export Datatable to Excel with Epplus
Compare Using Thread.Sleep and Timer for Delayed Execution
Fire-And-Forget with Async VS "Old Async Delegate"
Concurrent Hashset<T> in .Net Framework
Xml Serialization - Disable Rendering Root Element of Array
Why Does Casting Int to Invalid Enum Value Not Throw Exception
Regex for Accepting Only Persian Characters
Can a C# Thread Really Cache a Value and Ignore Changes to That Value on Other Threads
How to Access a Control Inside a Xaml Datatemplate
Writing to a Textbox from Another Thread
Calculating the Difference in Months Between Two Dates
How to Use Linq Contains(String[]) Instead of Contains(String)
Reading File Input from a Multipart/Form-Data Post