-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added XML map deserializer to bean mapper, creating "entry" with "key…
…" and "value".
- Loading branch information
Showing
11 changed files
with
616 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
src/main/java/com/norconex/commons/lang/bean/jackson/JsonXmlMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* Copyright 2023 Norconex Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.norconex.commons.lang.bean.jackson; | ||
|
||
import static java.lang.annotation.ElementType.FIELD; | ||
import static java.lang.annotation.ElementType.TYPE; | ||
import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.Target; | ||
import java.util.HashMap; | ||
|
||
import com.norconex.commons.lang.bean.BeanMapper; | ||
|
||
/** | ||
* <p> | ||
* This annotation must be used with the {@link JsonXmlMapModule}. | ||
* The module is registered automatically for XML source if you are using | ||
* {@link BeanMapper}. | ||
* Use this annotation only when you wish to overwrite default settings | ||
* via its attributes. It is otherwise of no use. | ||
* </p> | ||
* @since 3.0.0 | ||
*/ | ||
@Retention(RUNTIME) | ||
@Target({ TYPE, FIELD }) | ||
public @interface JsonXmlMap { | ||
|
||
/** | ||
* Field name to use for each map entries when serializing as XML. | ||
* This name does not affect reading. Default is "entry". | ||
* @return entry field name, when writing XML | ||
*/ | ||
public String entryName() default "entry"; | ||
|
||
/** | ||
* Field name to use for each map entry keys when serializing as XML. | ||
* Default is "key". | ||
* @return entry key field name, when writing XML | ||
*/ | ||
public String keyName() default "key"; | ||
|
||
/** | ||
* Field name to use for each map entry values when serializing as XML. | ||
* Default is "value". | ||
* @return entry value field name, when writing XML | ||
*/ | ||
public String valueName() default "value"; | ||
|
||
/** | ||
* Concrete Map type to use when deserializing. Has to be assignable | ||
* to the type of your Map property. Default will try to detect and | ||
* use {@link HashMap} as fallback. | ||
* @return map concrete type | ||
*/ | ||
public Class<?> mapType() default Void.class; | ||
|
||
/** | ||
* Concrete key type to use when deserializing. Has to be assignable | ||
* to the type of your Map entry key property. | ||
* Default will try to detect. | ||
* @return map entry key concrete type | ||
*/ | ||
public Class<?> keyType() default Void.class; | ||
|
||
/** | ||
* Concrete value type to use when deserializing. Has to be assignable | ||
* to the type of your Map entry value property. | ||
* Default will try to detect. | ||
* @return map entry value concrete type | ||
*/ | ||
public Class<?> valueType() default Void.class; | ||
} |
159 changes: 159 additions & 0 deletions
159
src/main/java/com/norconex/commons/lang/bean/jackson/JsonXmlMapDeserializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/* Copyright 2024 Norconex Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.norconex.commons.lang.bean.jackson; | ||
|
||
import static org.apache.commons.lang3.StringUtils.defaultIfBlank; | ||
|
||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonToken; | ||
import com.fasterxml.jackson.databind.BeanProperty; | ||
import com.fasterxml.jackson.databind.DeserializationContext; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.deser.ContextualDeserializer; | ||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; | ||
import com.norconex.commons.lang.ClassUtil; | ||
|
||
/** | ||
* Deserializes a list of "entry" elements each having a "key" and "value" | ||
* child elements into a Map. | ||
* @param <T> Map concrete type | ||
*/ | ||
public class JsonXmlMapDeserializer<T extends Map<?, ?>> | ||
extends StdDeserializer<T> | ||
implements ContextualDeserializer { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
private transient BeanProperty currentProperty; | ||
private JavaType mapType; | ||
|
||
public JsonXmlMapDeserializer() { | ||
this((Class<T>) null); | ||
} | ||
|
||
public JsonXmlMapDeserializer(Class<T> vc) { | ||
super(vc); | ||
} | ||
|
||
private JsonXmlMapDeserializer( | ||
BeanProperty currentProperty, JavaType mapType) { | ||
super(Map.class); | ||
this.currentProperty = currentProperty; | ||
this.mapType = mapType; | ||
} | ||
|
||
@Override | ||
public StdDeserializer<?> createContextual( | ||
DeserializationContext ctxt, BeanProperty property) { | ||
return new JsonXmlMapDeserializer<>( | ||
property, | ||
property != null | ||
? property.getType() | ||
: ctxt.constructType(Map.class)); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
@Override | ||
public T deserialize(JsonParser p, DeserializationContext ctxt) | ||
throws IOException { | ||
var map = createMap(); | ||
var keyName = "key"; | ||
var valueName = "value"; | ||
Class<?> keyType = null; | ||
Class<?> valueType = null; | ||
if (currentProperty != null) { | ||
var annot = currentProperty.getAnnotation(JsonXmlMap.class); | ||
if (annot != null) { | ||
keyName = defaultIfBlank(annot.keyName(), keyName); | ||
valueName = defaultIfBlank(annot.valueName(), valueName); | ||
if (!Void.class.equals(annot.keyType())) { | ||
keyType = annot.keyType(); | ||
} | ||
if (!Void.class.equals(annot.valueType())) { | ||
valueType = annot.valueType(); | ||
} | ||
} | ||
} | ||
|
||
while (p.nextToken() != JsonToken.END_OBJECT) { | ||
|
||
if (p.getCurrentToken() == JsonToken.START_OBJECT) { | ||
Object key = null; | ||
Object value = null; | ||
|
||
while (p.nextToken() != JsonToken.END_OBJECT) { | ||
var fieldName = p.currentName(); | ||
p.nextToken(); // Move to the value of the field | ||
|
||
if (keyName.equals(fieldName)) { | ||
key = ctxt.readValue(p, | ||
fieldType(keyType, mapType.containedType(0))); | ||
} else if (valueName.equals(fieldName)) { | ||
value = ctxt.readValue(p, | ||
fieldType(valueType, mapType.containedType(1))); | ||
} | ||
} | ||
|
||
if (key != null) { | ||
map.put(key, value); | ||
} | ||
} | ||
} | ||
return (T) map; | ||
} | ||
|
||
private Class<?> fieldType(Class<?> annotType, JavaType containedType) { | ||
if (annotType != null) { | ||
return annotType; | ||
} | ||
if (containedType != null) { | ||
return containedType.getRawClass(); | ||
} | ||
return null; | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private Map<Object, Object> createMap() { | ||
// Map type established in this priority order: | ||
// - specified on annotation | ||
// - actual type detected and instantiable | ||
// - Fall-back to HashMap | ||
|
||
// from annotation | ||
if (currentProperty != null) { | ||
var annot = currentProperty.getAnnotation(JsonXmlMap.class); | ||
if (annot != null && !Void.class.equals(annot.mapType())) { | ||
return (Map<Object, Object>) ClassUtil.newInstance( | ||
annot.mapType()); | ||
} | ||
} | ||
|
||
// from actual type | ||
try { | ||
var map = ClassUtil.newInstance(mapType.getRawClass()); | ||
if (map != null) { | ||
return (Map<Object, Object>) map; | ||
} | ||
} catch (Exception e) { | ||
// swallow | ||
} | ||
|
||
return ClassUtil.newInstance(HashMap.class); | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/main/java/com/norconex/commons/lang/bean/jackson/JsonXmlMapModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* Copyright 2023-2024 Norconex Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.norconex.commons.lang.bean.jackson; | ||
|
||
import java.util.Map; | ||
|
||
import com.fasterxml.jackson.databind.module.SimpleModule; | ||
import com.norconex.commons.lang.bean.BeanMapper; | ||
|
||
/** | ||
* Jackson module providing (de)serializers for Map, so they can be | ||
* written and read as XML without special hacks. | ||
* Already registered in {@link BeanMapper}. | ||
* @since 3.0.0 | ||
*/ | ||
public class JsonXmlMapModule extends SimpleModule { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
@SuppressWarnings("unchecked") | ||
public JsonXmlMapModule() { | ||
this.addSerializer((Class<Map<?, ?>>) (Class<?>) Map.class, | ||
new JsonXmlMapSerializer<>()); | ||
this.addDeserializer(Map.class, new JsonXmlMapDeserializer<>()); | ||
} | ||
} |
Oops, something went wrong.