Use JAXB XMLAnyElement type of style to return dynamic element names

前端 未结 2 1554
悲&欢浪女
悲&欢浪女 2020-12-18 14:02

I have read a lot of answers in these forums, as well as other blog posts, but I can\'t quite seem to connect the pieces together.

So, we start with a basic POJO con

相关标签:
2条回答
  • 2020-12-18 14:33

    Depends on the solution listed above, I've came into this MapAdapter that addresses both marshaling and unmarshalling process.

    @XmlType
    public class MapWrapper{
        private List<JAXBElement<String>> properties = new ArrayList<>();
    
        public MapWrapper(){
    
        }
        @XmlAnyElement
        public List<JAXBElement<String>> getProperties() {
            return properties;
        }
        public void setProperties(List<JAXBElement<String>> properties) {
            this.properties = properties;
        }
        @Override
        public String toString() {
            return "MapWrapper [properties=" + toMap() + "]";
        }
    
    
        public Map<String, String> toMap(){
            //Note: Due to type erasure, you cannot use properties.stream() directly when unmashalling is used.
            List<?> props = properties;
            return props.stream().collect(Collectors.toMap(MapWrapper::extractLocalName, MapWrapper::extractTextContent));
        }
    
        @SuppressWarnings("unchecked")
        private static String extractLocalName(Object obj){
    
            Map<Class<?>, Function<? super Object, String>> strFuncs = new HashMap<>();
            strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement<String>)jaxb).getName().getLocalPart());
            strFuncs.put(Element.class, ele -> ((Element) ele).getLocalName());
            return extractPart(obj, strFuncs).orElse("");
        }
    
    
        @SuppressWarnings("unchecked")
        private static String extractTextContent(Object obj){
            Map<Class<?>, Function<? super Object, String>> strFuncs = new HashMap<>();
            strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement<String>)jaxb).getValue());
            strFuncs.put(Element.class, ele -> ((Element) ele).getTextContent());
            return extractPart(obj, strFuncs).orElse("");
        }
    
        private static <ObjType, T> Optional<T> extractPart(ObjType obj, Map<Class<?>, Function<? super ObjType, T>> strFuncs){
            for(Class<?> clazz : strFuncs.keySet()){
                if(clazz.isInstance(obj)){
                    return Optional.of(strFuncs.get(clazz).apply(obj));
                }
            }
            return Optional.empty();
        }
    }
    
    public class MapAdapter extends XmlAdapter<MapWrapper, Map<String, String>>{
    
        @Override
        public Map<String, String> unmarshal(MapWrapper v) throws Exception {
            return v.toMap();
        }
    
        @Override
        public MapWrapper marshal(Map<String, String> m) throws Exception {
            MapWrapper wrapper = new MapWrapper();
    
            for(Map.Entry<String, String> entry : m.entrySet()){
                 wrapper.addEntry(new JAXBElement<String>(new QName(entry.getKey()), String.class, entry.getValue()));
            }
    
            return wrapper;
        }
    
    }
    

    I've post the full the full post here (in another post), where comments as well as examples are also provided..

    0 讨论(0)
  • 2020-12-18 14:37

    I wasn't so far from the answer - after a bit more of experimenting I found the right combo.

    Create a wrapper class for the un-mapable return type. Wrapper should contain/return List<JAXBElement<String> Annotate wrapper return type with @XmlAnyElement.

    public class MapWrapper {
       @XmlAnyElement
        public List<JAXBElement<String>> properties = new ArrayList<JAXBElement<String>>();
    }
    

    Create an XmlAdapter that marshals to the MapWrapper

    public class MapAdapter extends XmlAdapter<MapWrapper, Map<String,String>> {
        @Override
        public MapWrapper marshal(Map<String,String> m) throws Exception {
        MapWrapper wrapper = new MapWrapper();
        List<JAXBElement<String>> elements = new ArrayList<JAXBElement<String>>();
           for (Map.Entry<String, String> property: m.entrySet()) {
              elements.add(new JAXBElement<String>(
                        new QName(getCleanLabel(property.getKey())), 
              String.class, property.getValue()));
           }
           wrapper.elements=elements;
        return wrapper;
    }
    
    @Override
    public Map<String,String> unmarshal(MapWrapper v) throws Exception {
                // TODO
        throw new OperationNotSupportedException();
    }
    
    // Return a lower-camel XML-safe attribute
    private String getCleanLabel(String attributeLabel) {
        attributeLabel = attributeLabel.replaceAll("[()]", "")
                .replaceAll("[^\\w\\s]", "_").replaceAll(" ", "_")
                .toUpperCase();
        attributeLabel = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,
                attributeLabel);
        return attributeLabel;
    }
    }
    

    Annotate your unmappable type with the XmlAdapter

    @XmlRootElement
    public class SomeBean {
    
        @XmlJavaTypeAdapter(MapAdapter.class)
        public LinkedHashMap<String, String> getProperties() {
            return properties;
        }
    }
    

    A map like:

    My Property 1    My Value 1 
    My Property 2    My Value 2
    

    Should come out as:

    <someBean>
       <properties>
         <myProperty1>My Value 1</myProperty1>
         <myProperty2>My Value 1</myProperty2>
       </properties>
    </someBean>
    

    Hope this helps someone else!

    0 讨论(0)
提交回复
热议问题