question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Map deserialization results in different numeric classes based on json ordering (BigDecimal / Double) when used in combination with @JsonSubTypes

See original GitHub issue

Describe the bug When deserializing a jsonSubTye which contains a map with numeric values (floats) the order of the json string results in different classes constructed.

I have pasted a complete Junit5 Test which can easily be used to reproduce the issue.

Version information Happens from 2.12.0 onwards, in 2.11.x its ok.

To Reproduce If you have a way to reproduce this with:

package test;

import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

@TestInstance(Lifecycle.PER_CLASS)
public class JacksonSubTypeBehaviourTest {

	private static final Logger log = LoggerFactory.getLogger(JacksonSubTypeBehaviourTest.class);

	private ObjectMapper mapper;

	@BeforeAll
	public void before() {
		mapper = new ObjectMapper();
		mapper.disable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
	}

	@Test 
	public void testDeserializeWithDifferentOrdering() throws Exception {
		
		String ordering1 = "{\n"
				+ "			\"type\": \"MAP\",\n"
				+ "			\"map\": {\n"
				+ "				\"doubleValue\": 0.1\n"
				+ "			}\n"
				+ "}";
				
		TestMapContainer model1 = mapper.readValue(ordering1, TestMapContainer.class);
		log.info("clazz: {}",  model1.getMap().get("doubleValue").getClass());
		Assertions.assertTrue(model1.getMap().get("doubleValue") instanceof Double);
		
		
		String ordering2 = "{\n"
				+ "	\"map\": {\n"
				+ "		\"doubleValue\": 0.1\n"
				+ "	},\n"
				+ "	\"type\": \"MAP\"\n"
				+ "	\n"
				+ "}";
		
		TestMapContainer model2 = mapper.readValue(ordering2, TestMapContainer.class);
		
		log.info("clazz: {}",  model2.getMap().get("doubleValue").getClass());
		Assertions.assertTrue(model2.getMap().get("doubleValue") instanceof Double);
		
	}
	
	@JsonTypeInfo(  
		    use = JsonTypeInfo.Id.NAME,  
		    include = JsonTypeInfo.As.PROPERTY,  
		    property = "type")

	@JsonSubTypes({  
	    @Type(value = TestMapContainer.class, name = "MAP"),
	     })  	
	private static interface TestJsonTypeInfoInterface {
		
	}
	
	private static class TestMapContainer implements TestJsonTypeInfoInterface {
		
		private Map<String, ? extends Object> map = new HashMap<>();

		public Map<String, ? extends Object> getMap() {
			return map;
		}

		public void setMap(Map<String, ? extends Object> map) {
			this.map = map;
		}
		
	}
}

Expected behavior Numeric values (point numbers) should always be created as Double unless stated otherwise with DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
cowtowncodercommented, Oct 9, 2021

The difference is meant to defer possible truncation/coercion in case of buffering (since we have no idea what the target type is); whereas when using UntypedObjectDeserializer – which has target type of java.lang.Object – we know what target type we should be using, based on configuration.

This matter more for binary formats where there is specific accurate representation, but is more problematic for textual formats that do not have distinction between various FP representations. And then there is the difference between streaming-level and databind-level configuration to consider too.

Ideally, I think, the whole decoding should be deferred in buffering cases: parser would simply validate that the textual representation is valid JSON (or xml, yaml, csv etc) Number, but would not try to get Java Number out of it until explicitly requested.

0reactions
cowtowncodercommented, Oct 18, 2022

@mreiterer I don’t think so due to timing constraints.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Jackson JSON - Deserializing as a subtype using 'as' attribute ...
The attribute @JsonDeserialize#as can be used to to deserialize to a specific subtype of declared type of a Java object.
Read more >
Using unsafe Jackson deserialization configuration is security ...
MINIMAL_CLASS) and so rely on @JsonTypeName and @JsonSubTypes . Sensitive Code Example. ObjectMapper mapper = new ObjectMapper(); mapper.enableDefaultTyping(); ...
Read more >
Java to Jackson JSON serialization: Money fields
I was thinking about making a Money class with a custom Jackson serializer... can you make a custom serializer for a field variable?...
Read more >
Index (Jackson JSON Processor) - Javadoc.io
Shared base class used for anything on which annotations (included within a ... if possible (when resulting serialization would use JSON Object).
Read more >
The Behavioral Diversity of Java JSON Libraries - DiVA Portal
Strings, Numbers, Booleans and null. The composite types are Object types that map String keys to values of any type, and an Array...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found