Skip to content

Commit 728f7fc

Browse files
committed
HHH-16280 Fix JacksonXmlFormatMapper handling of array data types
1 parent ff19a91 commit 728f7fc

File tree

3 files changed

+59
-5
lines changed

3 files changed

+59
-5
lines changed

hibernate-core/hibernate-core.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ dependencies {
6868
testRuntimeOnly testLibs.weld
6969
testRuntimeOnly testLibs.wildFlyTxnClient
7070
testRuntimeOnly libs.jackson
71+
testRuntimeOnly libs.jacksonXml
72+
testRuntimeOnly libs.jacksonJsr310
7173

7274
testAnnotationProcessor project( ':hibernate-jpamodelgen' )
7375

hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java

+56-5
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@
66
*/
77
package org.hibernate.type.format.jackson;
88

9-
import org.hibernate.type.format.FormatMapper;
9+
import java.io.IOException;
10+
import java.lang.reflect.Type;
11+
import java.util.ArrayList;
12+
1013
import org.hibernate.type.descriptor.WrapperOptions;
1114
import org.hibernate.type.descriptor.java.JavaType;
15+
import org.hibernate.type.format.FormatMapper;
1216

17+
import com.fasterxml.jackson.core.JsonParser;
1318
import com.fasterxml.jackson.core.JsonProcessingException;
19+
import com.fasterxml.jackson.core.JsonToken;
20+
import com.fasterxml.jackson.databind.DeserializationContext;
21+
import com.fasterxml.jackson.databind.JsonDeserializer;
1422
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import com.fasterxml.jackson.databind.SerializationFeature;
24+
import com.fasterxml.jackson.databind.module.SimpleModule;
1525
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
26+
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
1627

1728
/**
1829
* @author Christian Beikov
@@ -24,20 +35,37 @@ public final class JacksonXmlFormatMapper implements FormatMapper {
2435
private final ObjectMapper objectMapper;
2536

2637
public JacksonXmlFormatMapper() {
27-
this(new XmlMapper());
38+
this( createXmlMapper() );
2839
}
2940

3041
public JacksonXmlFormatMapper(ObjectMapper objectMapper) {
3142
this.objectMapper = objectMapper;
3243
}
3344

45+
private static XmlMapper createXmlMapper() {
46+
final XmlMapper xmlMapper = new XmlMapper();
47+
// needed to automatically find and register Jackson's jsr310 module for java.time support
48+
xmlMapper.findAndRegisterModules();
49+
xmlMapper.configure( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false );
50+
xmlMapper.enable( ToXmlGenerator.Feature.WRITE_NULLS_AS_XSI_NIL );
51+
// Workaround for null vs empty string handling inside arrays,
52+
// see: https://github.com/FasterXML/jackson-dataformat-xml/issues/344
53+
final SimpleModule module = new SimpleModule();
54+
module.addDeserializer( String[].class, new StringArrayDeserializer() );
55+
xmlMapper.registerModule( module );
56+
return xmlMapper;
57+
}
58+
3459
@Override
3560
public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) {
3661
if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) {
3762
return (T) charSequence.toString();
3863
}
3964
try {
40-
return objectMapper.readValue( charSequence.toString(), objectMapper.constructType( javaType.getJavaType() ) );
65+
return objectMapper.readValue(
66+
charSequence.toString(),
67+
objectMapper.constructType( javaType.getJavaType() )
68+
);
4169
}
4270
catch (JsonProcessingException e) {
4371
throw new IllegalArgumentException( "Could not deserialize string to java type: " + javaType, e );
@@ -49,12 +77,35 @@ public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapper
4977
if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) {
5078
return (String) value;
5179
}
80+
else if ( javaType.getJavaTypeClass().isArray() ) {
81+
if ( javaType.getJavaTypeClass().getComponentType().isEnum() ) {
82+
// for enum arrays we need to explicitly pass Byte[] as the writer type
83+
return writeValueAsString( value, javaType, Byte[].class );
84+
}
85+
}
86+
return writeValueAsString( value, javaType, javaType.getJavaType() );
87+
}
88+
89+
private <T> String writeValueAsString(Object value, JavaType<T> javaType, Type type) {
5290
try {
53-
return objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) )
54-
.writeValueAsString( value );
91+
return objectMapper.writerFor( objectMapper.constructType( type ) ).writeValueAsString( value );
5592
}
5693
catch (JsonProcessingException e) {
5794
throw new IllegalArgumentException( "Could not serialize object of java type: " + javaType, e );
5895
}
5996
}
97+
98+
private static class StringArrayDeserializer extends JsonDeserializer<String[]> {
99+
@Override
100+
public String[] deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException {
101+
final ArrayList<String> result = new ArrayList<>();
102+
JsonToken token;
103+
while ( ( token = jp.nextValue() ) != JsonToken.END_OBJECT ) {
104+
if ( token.isScalarValue() ) {
105+
result.add( jp.getValueAsString() );
106+
}
107+
}
108+
return result.toArray( String[]::new );
109+
}
110+
}
60111
}

settings.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ dependencyResolutionManagement {
8484

8585
alias( "jackson" ).to ( "com.fasterxml.jackson.core", "jackson-databind" ).version( "2.14.1" )
8686
alias( "jacksonXml" ).to ( "com.fasterxml.jackson.dataformat", "jackson-dataformat-xml" ).version( "2.14.1" )
87+
alias( "jacksonJsr310" ).to( "com.fasterxml.jackson.datatype", "jackson-datatype-jsr310" ).version( "2.14.1" )
8788
alias( "validator" ).to( "org.hibernate.validator", "hibernate-validator" ).version( "7.0.4.Final" )
8889

8990
alias( "ant" ).to( "org.apache.ant", "ant" ).version( "1.8.2" )

0 commit comments

Comments
 (0)