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

EnumSet deserialization does not work when we activate default typing in ObjectMapper #4214

Closed
1 task done
dvhvsekhar opened this issue Nov 23, 2023 · 5 comments
Closed
1 task done
Labels
2.17 Issues planned at earliest for 2.17
Milestone

Comments

@dvhvsekhar
Copy link
Contributor

dvhvsekhar commented Nov 23, 2023

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

If we serialize an object which has a property of type Set, but referring EnumSet at runtime, then serialization uses CollectionSerializer instead of EnumSetSerializer for this property, when we activate default typing in ObjectMapper. This causes deserialization (using EnumSetDeserializer) to fail, since EnumSetDeserializer expects encoding of EnumSetSerializer, but not of CollectionSerializer.

Version Information

2.14.0 and above

Reproduction

If we run the below given test (java code), we get following error:

[ERROR] EnumSetSerializationTest.testSerialization:69 » MismatchedInput Cannot deserialize value of type com.fasterxml.jackson.databind.ser.enums.EnumSetSerializationTest$MyEnum from Array value (token JsonToken.START_ARRAY)
at [Source: REDACTED (StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION disabled); line: 1, column: 191] (through reference chain: com.fasterxml.jackson.databind.ser.enums.EnumSetSerializationTest$EnumSetHolder["enumSet"]->java.util.RegularEnumSet[0])

package com.fasterxml.jackson.databind.ser.enums;

import com.fasterxml.jackson.databind.BaseMapTest;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.EnumSet;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;

public class EnumSetSerializationTest extends BaseMapTest {

	static enum MyEnum {
		ITEM_A, ITEM_B;
	}

	static class EnumSetHolder {
		private Set<MyEnum> enumSet; // use Set instead of EnumSet for type of this

		public Set<MyEnum> getEnumSet() {
			return enumSet;
		}

		public void setEnumSet(Set<MyEnum> enumSet) {
			this.enumSet = enumSet;
		}

		@Override
		public boolean equals(Object o) {
			if (!(o instanceof EnumSetHolder)) {
				return false;
			}
			EnumSetHolder eh = (EnumSetHolder) o;
			if (eh == this) {
				return true;
			}
			if (enumSet == null) {
				if (eh.getEnumSet() == null) {
					return true;
				} else {
					return false;
				}
			} else {
				if (eh.getEnumSet() == null) {
					return false;
				}
				return enumSet.containsAll(eh.getEnumSet());
			}
		}
	}

	public void testSerialization() throws Exception {
		ObjectMapper mapper = jsonMapperBuilder().build()
				.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
				// this type information is needed in json string, for this test
				.activateDefaultTyping(BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build(),
						DefaultTyping.EVERYTHING);

		EnumSetHolder enumSetHolder = new EnumSetHolder();
		enumSetHolder.setEnumSet(EnumSet.allOf(MyEnum.class));
		String jsonStr = mapper.writeValueAsString(enumSetHolder);
		EnumSetHolder result = mapper.readValue(jsonStr, EnumSetHolder.class);
		assertEquals(result, enumSetHolder);
	}
}

Expected behavior

Above given test code should pass.

Additional context

As a workaround, if we make type of EnumSetHolder#enumSet property as EnumSet instead of Set, in the above given code, test passes.

No response

@dvhvsekhar dvhvsekhar added the to-evaluate Issue that has been received but not yet evaluated label Nov 23, 2023
@dvhvsekhar
Copy link
Contributor Author

I proposed a fix with PR - #4215

@cowtowncoder
Copy link
Member

Ok, now we have a reproduction at least. Looks like Enums do not handle polymorphic deserialization well at this point -- probably since expectation wasn't that they'd ever be polymorphic.
Before addition of DefaultTyping.EVERYTHING...
(I did change test to use new DefaultTyping.NON_FINAL_AND_ENUMS which fails similarly, fwtw)

@cowtowncoder cowtowncoder added 2.17 Issues planned at earliest for 2.17 and removed to-evaluate Issue that has been received but not yet evaluated labels Nov 27, 2023
@cowtowncoder
Copy link
Member

Ok, actually, it's not so much EnumDeserializer that is not playing nicely, but rather EnumSetDeserializer... since that would have to create and use TypeDeserializer for Enum values.
This is what basic CollectionDeserializer does.

I don't know if I have time to work much more on this issue but if anyone wants to try to figure it out would be happy to review PR.

@cowtowncoder
Copy link
Member

Ok, one more note: it looks like BasicDeserializerFactory already has TypeDeserializer to pass when constructing EnumSetDeserializer. So maybe I might be able to provide a fix. Fix though will probably need to go in 2.17 since (minor) internal API change needed.
But fix seems quite doable.

cowtowncoder added a commit that referenced this issue Nov 27, 2023
@cowtowncoder cowtowncoder changed the title EnumSet Deserialization does not work when we activate default typing in ObjectMapper EnumSet deserialization does not work when we activate default typing in ObjectMapper Nov 27, 2023
@cowtowncoder cowtowncoder added this to the 2.17.0 milestone Nov 28, 2023
@cowtowncoder
Copy link
Member

Was able to fix; deprecated older methods, fix will be in 2.17.0.

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

No branches or pull requests

2 participants