HHH-16280 Fix JacksonXmlFormatMapper handling of array data types

This commit is contained in:
Marco Belladelli 2023-03-10 14:37:23 +01:00 committed by Steve Ebersole
parent e675962779
commit c6fa754f06
3 changed files with 59 additions and 5 deletions

View File

@ -72,6 +72,8 @@ dependencies {
testRuntimeOnly testLibs.weld testRuntimeOnly testLibs.weld
testRuntimeOnly testLibs.wildFlyTxnClient testRuntimeOnly testLibs.wildFlyTxnClient
testRuntimeOnly libs.jackson testRuntimeOnly libs.jackson
testRuntimeOnly libs.jacksonXml
testRuntimeOnly libs.jacksonJsr310
testAnnotationProcessor project( ':hibernate-jpamodelgen' ) testAnnotationProcessor project( ':hibernate-jpamodelgen' )

View File

@ -6,13 +6,24 @@
*/ */
package org.hibernate.type.format.jackson; package org.hibernate.type.format.jackson;
import org.hibernate.type.format.FormatMapper; import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.format.FormatMapper;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
/** /**
* @author Christian Beikov * @author Christian Beikov
@ -24,20 +35,37 @@ public final class JacksonXmlFormatMapper implements FormatMapper {
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public JacksonXmlFormatMapper() { public JacksonXmlFormatMapper() {
this(new XmlMapper()); this( createXmlMapper() );
} }
public JacksonXmlFormatMapper(ObjectMapper objectMapper) { public JacksonXmlFormatMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
} }
private static XmlMapper createXmlMapper() {
final XmlMapper xmlMapper = new XmlMapper();
// needed to automatically find and register Jackson's jsr310 module for java.time support
xmlMapper.findAndRegisterModules();
xmlMapper.configure( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false );
xmlMapper.enable( ToXmlGenerator.Feature.WRITE_NULLS_AS_XSI_NIL );
// Workaround for null vs empty string handling inside arrays,
// see: https://github.com/FasterXML/jackson-dataformat-xml/issues/344
final SimpleModule module = new SimpleModule();
module.addDeserializer( String[].class, new StringArrayDeserializer() );
xmlMapper.registerModule( module );
return xmlMapper;
}
@Override @Override
public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) { public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) {
if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) { if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) {
return (T) charSequence.toString(); return (T) charSequence.toString();
} }
try { try {
return objectMapper.readValue( charSequence.toString(), objectMapper.constructType( javaType.getJavaType() ) ); return objectMapper.readValue(
charSequence.toString(),
objectMapper.constructType( javaType.getJavaType() )
);
} }
catch (JsonProcessingException e) { catch (JsonProcessingException e) {
throw new IllegalArgumentException( "Could not deserialize string to java type: " + javaType, e ); throw new IllegalArgumentException( "Could not deserialize string to java type: " + javaType, e );
@ -49,12 +77,35 @@ public final class JacksonXmlFormatMapper implements FormatMapper {
if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) { if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) {
return (String) value; return (String) value;
} }
else if ( javaType.getJavaTypeClass().isArray() ) {
if ( javaType.getJavaTypeClass().getComponentType().isEnum() ) {
// for enum arrays we need to explicitly pass Byte[] as the writer type
return writeValueAsString( value, javaType, Byte[].class );
}
}
return writeValueAsString( value, javaType, javaType.getJavaType() );
}
private <T> String writeValueAsString(Object value, JavaType<T> javaType, Type type) {
try { try {
return objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ) return objectMapper.writerFor( objectMapper.constructType( type ) ).writeValueAsString( value );
.writeValueAsString( value );
} }
catch (JsonProcessingException e) { catch (JsonProcessingException e) {
throw new IllegalArgumentException( "Could not serialize object of java type: " + javaType, e ); throw new IllegalArgumentException( "Could not serialize object of java type: " + javaType, e );
} }
} }
private static class StringArrayDeserializer extends JsonDeserializer<String[]> {
@Override
public String[] deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException {
final ArrayList<String> result = new ArrayList<>();
JsonToken token;
while ( ( token = jp.nextValue() ) != JsonToken.END_OBJECT ) {
if ( token.isScalarValue() ) {
result.add( jp.getValueAsString() );
}
}
return result.toArray( String[]::new );
}
}
} }

View File

@ -84,6 +84,7 @@ dependencyResolutionManagement {
alias( "jackson" ).to ( "com.fasterxml.jackson.core", "jackson-databind" ).version( "2.14.1" ) alias( "jackson" ).to ( "com.fasterxml.jackson.core", "jackson-databind" ).version( "2.14.1" )
alias( "jacksonXml" ).to ( "com.fasterxml.jackson.dataformat", "jackson-dataformat-xml" ).version( "2.14.1" ) alias( "jacksonXml" ).to ( "com.fasterxml.jackson.dataformat", "jackson-dataformat-xml" ).version( "2.14.1" )
alias( "jacksonJsr310" ).to( "com.fasterxml.jackson.datatype", "jackson-datatype-jsr310" ).version( "2.14.1" )
alias( "validator" ).to( "org.hibernate.validator", "hibernate-validator" ).version( "8.0.0.Final" ) alias( "validator" ).to( "org.hibernate.validator", "hibernate-validator" ).version( "8.0.0.Final" )
alias( "ant" ).to( "org.apache.ant", "ant" ).version( "1.8.2" ) alias( "ant" ).to( "org.apache.ant", "ant" ).version( "1.8.2" )