Description
Problem
We have an AVRO data that we want to evaluate with a rule value.pet == null
to preserve int and doubles we have chosen to convert to Java Map rather that JSON before passing to CEL evaluator.
For this I have converted avro into a java map to be passed into CEL ENGINE.
The conversion logic looks something like this
public static Map<String, Object> avroToMap(GenericRecord genericRecord) {
Map<String, Object> map = new HashMap<>();
genericRecord.getSchema().getFields().forEach(field -> {
Object value = genericRecord.get(field.name());
switch (value) {
case null -> map.put(field.name(), null);
case Integer i -> map.put(field.name(), i.longValue());
case Utf8 utf8 -> map.put(field.name(), utf8.toString());
case GenericRecord nested -> map.put(field.name(), avroToMap(nested));
case GenericData.Array<?> array -> {
List<Object> list = new ArrayList<>();
for (Object elem : array) {
list.add(avroToMap((GenericRecord) elem));
}
map.put(field.name(), list);
}
default -> map.put(field.name(), value);
}
});
return map;
It works fine for most cases accept null check eg value.pet == null
when the value is set to null in the map.
This causes cel engine to return AutoValue_CelUnknownSet
The map does represent a null value in the map correctly and the expression evaluates
has(value.pet)
returns true'pet' in value
return falsetype(value.pet) == null_type
returns CelUnknownSetvalue.pet == null
returns CelUnknownSet
When looking at selectField I can see that it's extracting null from the map correcty.
Which then get's feed into IntermediateResult.create
But before that valueOrUnknown Checks if the value is null or instance of CelUnknownSet. Hence it why null value returns a CelUnknownSet?
What i don't understand is how do we set the field to a null value and get boolean evaluation on it.
Solution ?
After some further poking around and comparing JSON and JAVA MAP being passed into CEL. I have noticed the following difference.
When debugging inside CEL, and looking at the map type inside selectField
AVRO converted to JSON.
"pet" -> {NullValue}<NULL_VALUE>
AVRO to JAVA MAP with Java null
"pet" -> null
As we can see java null doesn't get recognised as a null value, but rather as an unknown field. This makes CEL return CelUnknownSet
when valueOrUnknown
is called. So null values needs to be set to something else so that it is interpreted as null correctly
When using NullValue.NULL_VALUE
from import com.google.protobuf.NullValue;
We get the expected null type in CEL
"pet" -> {NullValue}<NULL_VALUE>
This seems a bit counter intuitive and not explicitly clear when passing a JAVA Map, which can cause issues. Would this be a correct way of handling this or have I missed something?