How to Ignore Unknown Enum Values During JSON Deserialization

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



Leave a reply



Submit