IT박스

JAXB : 맵을 마샬링하는 방법

itboxs 2020. 11. 21. 14:20
반응형

JAXB : 맵을 마샬링하는 방법


질문은 JAXB 맵 마샬링에 관한 것입니다. 맵을 다음과 같은 구조로 마샬링하는 방법에 대한 많은 예제가 있습니다.

<map>
  <entry>
    <key> KEY </key>
    <value> VALUE </value>
  </entry>
  <entry>
    <key> KEY2 </key>
    <value> VALUE2 </value>
  </entry>
  <entry>
  ...
</map>

실제로 이것은 JAXB에서 기본적으로 지원됩니다. 그러나 내가 필요한 것은 키가 요소 이름이고 값이 그 내용 인 XML입니다.

<map>
  <key> VALUE </key>
  <key2> VALUE2 </key2>
 ...
</map>

JAXB 개발자 ( https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html ) 가 권장하는 방식으로 맵 어댑터를 구현하는 데 성공하지 못했습니다. 필요에 따라 동적 속성 이름 :)

그것에 대한 해결책이 있습니까?

추신 현재 저는 XML로 마샬링하려는 각각의 일반적인 키-값 쌍 세트에 대해 전용 컨테이너 클래스를 만들어야합니다. 작동하지만 이러한 도우미 컨테이너를 너무 많이 만들어야합니다.


제공된 코드가 작동하지 않았습니다. 지도에 대한 다른 방법을 찾았습니다.

MapElements :

package com.cellfish.mediadb.rest.lucene;

import javax.xml.bind.annotation.XmlElement;

class MapElements
{
  @XmlElement public String  key;
  @XmlElement public Integer value;

  private MapElements() {} //Required by JAXB

  public MapElements(String key, Integer value)
  {
    this.key   = key;
    this.value = value;
  }
}

MapAdapter :

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

import javax.xml.bind.annotation.adapters.XmlAdapter;

class MapAdapter extends XmlAdapter<MapElements[], Map<String, Integer>> {
    public MapElements[] marshal(Map<String, Integer> arg0) throws Exception {
        MapElements[] mapElements = new MapElements[arg0.size()];
        int i = 0;
        for (Map.Entry<String, Integer> entry : arg0.entrySet())
            mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());

        return mapElements;
    }

    public Map<String, Integer> unmarshal(MapElements[] arg0) throws Exception {
        Map<String, Integer> r = new HashMap<String, Integer>();
        for (MapElements mapelement : arg0)
            r.put(mapelement.key, mapelement.value);
        return r;
    }
}

rootElement :

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

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Root {

    private Map<String, Integer> mapProperty;

    public Root() {
        mapProperty = new HashMap<String, Integer>();
    }

    @XmlJavaTypeAdapter(MapAdapter.class)
    public Map<String, Integer> getMapProperty() {
        return mapProperty;
    }

    public void setMapProperty(Map<String, Integer> map) {
        this.mapProperty = map;
    }

}

이 웹 사이트에서 코드를 찾았습니다. http://www.developpez.net/forums/d972324/java/general-java/xml/hashmap-jaxb/


이 작업을 수행하려는 타당한 이유가있을 수 있지만 이러한 종류의 XML을 생성하는 것은 일반적으로 피하는 것이 가장 좋습니다. 왜? 이는지도의 XML 요소가지도의 런타임 콘텐츠에 종속된다는 것을 의미하기 때문입니다. 그리고 XML은 일반적으로 외부 인터페이스 또는 인터페이스 레이어로 사용되기 때문에 바람직하지 않습니다. 설명하겠습니다.

Xml 스키마 (xsd)는 XML 문서의 인터페이스 계약을 정의합니다. XSD에서 코드를 생성 할 수있을뿐만 아니라 JAXB는 코드에서 XML 스키마를 생성 할 수도 있습니다. 이를 통해 인터페이스를 통해 교환되는 데이터를 XSD에 정의 된 사전 합의 된 구조로 제한 할 수 있습니다.

의 기본 사례 Map<String, String>에서 생성 된 XSD는 각각 하나의 xs:string키와 하나의 xs:string값을 포함해야하는 여러 항목 요소를 포함하도록 맵 요소를 제한합니다 . 이것은 매우 명확한 인터페이스 계약입니다.

설명하는 것은 런타임시 맵의 내용에 따라 이름이 결정되는 요소를 xml 맵에 포함하려는 것입니다. 그런 다음 생성 된 XSD는 컴파일시 유형을 알 수없는 요소 목록이 맵에 포함되어야 함을 지정할 수만 있습니다. 이것은 일반적으로 인터페이스 계약을 정의 할 때 피해야하는 것입니다.

이 경우 엄격한 계약을 달성하려면 String 대신 열거 형을 맵의 키로 사용해야합니다.

public enum KeyType {
 KEY, KEY2;
}

@XmlJavaTypeAdapter(MapAdapter.class)
Map<KeyType , String> mapProperty;

이렇게하면 XML에서 요소가 되고자하는 키가 컴파일 타임에 알려 지므로 JAXB는 미리 정의 된 키 KEY 또는 KEY2 중 하나를 사용하여 요소로 맵 요소를 제한하는 스키마를 생성 할 수 있어야합니다.

반면에 기본 생성 구조를 단순화하려는 경우

<map>
    <entry>
        <key>KEY</key>
        <value>VALUE</value>
    </entry>
    <entry>
        <key>KEY2</key>
        <value>VALUE2</value>
    </entry>
</map>

이와 같이 더 간단한 것으로

<map>
    <item key="KEY" value="VALUE"/>
    <item key="KEY2" value="VALUE2"/>
</map>

다음과 같이 Map을 MapElements 배열로 변환하는 MapAdapter를 사용할 수 있습니다.

class MapElements {
    @XmlAttribute
    public String key;
    @XmlAttribute
    public String value;

    private MapElements() {
    } //Required by JAXB

    public MapElements(String key, String value) {
        this.key = key;
        this.value = value;
    }
}


public class MapAdapter extends XmlAdapter<MapElements[], Map<String, String>> {
    public MapAdapter() {
    }

    public MapElements[] marshal(Map<String, String> arg0) throws Exception {
        MapElements[] mapElements = new MapElements[arg0.size()];
        int i = 0;
        for (Map.Entry<String, String> entry : arg0.entrySet())
            mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());

        return mapElements;
    }

    public Map<String, String> unmarshal(MapElements[] arg0) throws Exception {
        Map<String, String> r = new TreeMap<String, String>();
        for (MapElements mapelement : arg0)
            r.put(mapelement.key, mapelement.value);
        return r;
    }
}

나는 여전히 더 나은 솔루션을 연구하고 있지만 MOXy JAXB 를 사용하여 다음 XML을 처리 할 수있었습니다.

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <mapProperty>
      <map>
         <key>value</key>
         <key2>value2</key2>
      </map>
   </mapProperty>
</root>

Map 속성에 @XmlJavaTypeAdapter를 사용해야합니다.

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

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Root {

    private Map<String, String> mapProperty;

    public Root() {
        mapProperty = new HashMap<String, String>();
    }

    @XmlJavaTypeAdapter(MapAdapter.class)
    public Map<String, String> getMapProperty() {
        return mapProperty;
    }

    public void setMapProperty(Map<String, String> map) {
        this.mapProperty = map;
    }

}

XmlAdapter의 구현은 다음과 같습니다.

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

import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class MapAdapter extends XmlAdapter<AdaptedMap, Map<String, String>> {

    @Override
    public AdaptedMap marshal(Map<String, String> map) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document document = db.newDocument();
        Element rootElement = document.createElement("map");
        document.appendChild(rootElement);

        for(Entry<String,String> entry : map.entrySet()) {
            Element mapElement = document.createElement(entry.getKey());
            mapElement.setTextContent(entry.getValue());
            rootElement.appendChild(mapElement);
        }

        AdaptedMap adaptedMap = new AdaptedMap();
        adaptedMap.setValue(document);
        return adaptedMap;
    }

    @Override
    public Map<String, String> unmarshal(AdaptedMap adaptedMap) throws Exception {
        Map<String, String> map = new HashMap<String, String>();
        Element rootElement = (Element) adaptedMap.getValue();
        NodeList childNodes = rootElement.getChildNodes();
        for(int x=0,size=childNodes.getLength(); x<size; x++) {
            Node childNode = childNodes.item(x);
            if(childNode.getNodeType() == Node.ELEMENT_NODE) {
                map.put(childNode.getLocalName(), childNode.getTextContent());
            }
        }
        return map;
    }

}

AdpatedMap 클래스는 모든 마법이 일어나는 곳입니다. 우리는 콘텐츠를 표현하기 위해 DOM을 사용할 것입니다. @XmlAnyElement와 Object 유형의 속성 조합을 통해 DOM을 다루는 JAXB 소개를 속일 것입니다.

import javax.xml.bind.annotation.XmlAnyElement;

public class AdaptedMap {

    private Object value;

    @XmlAnyElement
    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

}

이 솔루션에는 MOXy JAXB 구현이 필요합니다. 다음 항목이있는 모델 클래스와 함께 jaxb.properties 파일을 추가하여 MOXy 구현을 사용하도록 JAXB 런타임을 구성 할 수 있습니다.

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

다음 데모 코드를 사용하여 코드를 확인할 수 있습니다.

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Root root = (Root) unmarshaller.unmarshal(new File("src/forum74/input.xml"));

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }
}

나는 이것에 정말로 잘 대답하는 것을 보지 못했다. 여기서 꽤 잘 작동하는 것을 찾았습니다.

JAXB XMLAnyElement 유형의 스타일을 사용하여 동적 요소 이름을 리턴하십시오.

해시 맵 트리를 지원하기 위해 약간 수정했습니다. 다른 컬렉션을 추가 할 수 있습니다.

public class MapAdapter extends XmlAdapter<MapWrapper, Map<String, Object>> {
    @Override
    public MapWrapper marshal(Map<String, Object> m) throws Exception {
        MapWrapper wrapper = new MapWrapper();
        List elements = new ArrayList();
        for (Map.Entry<String, Object> property : m.entrySet()) {

            if (property.getValue() instanceof Map)
                elements.add(new JAXBElement<MapWrapper>(new QName(getCleanLabel(property.getKey())),
                        MapWrapper.class, marshal((Map) property.getValue())));
            else
                elements.add(new JAXBElement<String>(new QName(getCleanLabel(property.getKey())),
                        String.class, property.getValue().toString()));
        }
        wrapper.elements = elements;
        return wrapper;
    }

    @Override
    public Map<String, Object> unmarshal(MapWrapper v) throws Exception {
        // TODO
        throw new OperationNotSupportedException();
    }

    // Return a XML-safe attribute.  Might want to add camel case support
    private String getCleanLabel(String attributeLabel) {
        attributeLabel = attributeLabel.replaceAll("[()]", "").replaceAll("[^\\w\\s]", "_").replaceAll(" ", "_");
        return attributeLabel;
    }
}
class MapWrapper {
    @XmlAnyElement
    List elements;
}

그런 다음 구현하려면 :

static class myxml {
    String name = "Full Name";
    String address = "1234 Main St";
    // I assign values to the map elsewhere, but it's just a simple
    // hashmap with a hashmap child as an example.
    @XmlJavaTypeAdapter(MapAdapter.class)
    public Map<String, Object> childMap;
}

간단한 Marshaller를 통해 이것을 공급하면 다음과 같은 출력이 제공됩니다.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myxml>
    <name>Full Name</name>
    <address>1234 Main St</address>
    <childMap>
        <key2>value2</key2>
        <key1>value1</key1>
        <childTree>
            <childkey1>childvalue1</childkey1>
        </childTree>
    </childMap>
</myxml>

(죄송합니다. 댓글을 추가 할 수 없습니다)

위의 Blaise의 답변에서 변경하는 경우 :

@XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, String> getMapProperty() {
    return mapProperty;
}

에:

@XmlJavaTypeAdapter(MapAdapter.class)
@XmlPath(".") // <<-- add this
public Map<String, String> getMapProperty() {
    return mapProperty;
}

그러면 <mapProperty>태그 가 제거 되고 다음을 제공해야합니다.

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <map>
        <key>value</key>
        <key2>value2</key2>
    </map>
</root>

또는 :

다음과 같이 변경할 수도 있습니다.

@XmlJavaTypeAdapter(MapAdapter.class)
@XmlAnyElement // <<-- add this
public Map<String, String> getMapProperty() {
    return mapProperty;
}

그런 다음 AdaptedMap모두 제거 MapAdapter하고 Document객체에 직접 마샬링하도록 변경할 있습니다. 마샬링으로 만 테스트 했으므로 마샬링 문제가있을 수 있습니다.

이에 대한 전체 예를 들어보고 그에 따라이 게시물을 편집 할 시간을 찾아 보겠습니다.


어댑터가없는 솔루션이 있습니다. xml 요소로 변환 된 임시 맵 및 그 반대 :

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "SchemaBasedProperties")
public class SchemaBasedProperties
{
  @XmlTransient
  Map<String, Map<String, String>> properties;

  @XmlAnyElement(lax = true)
  List<Object> xmlmap;

  public Map<String, Map<String, String>> getProperties()
  {
    if (properties == null)
      properties = new LinkedHashMap<String, Map<String, String>>(); // I want same order

    return properties;
  }

  boolean beforeMarshal(Marshaller m)
  {
    try
    {
      if (properties != null && !properties.isEmpty())
      {
        if (xmlmap == null)
          xmlmap = new ArrayList<Object>();
        else
          xmlmap.clear();

        javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
        javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
        org.w3c.dom.Document doc = db.newDocument();
        org.w3c.dom.Element element;

        Map<String, String> attrs;

        for (Map.Entry<String, Map<String, String>> it: properties.entrySet())
        {
          element = doc.createElement(it.getKey());
          attrs = it.getValue();

          if (attrs != null)
            for (Map.Entry<String, String> at: attrs.entrySet())
              element.setAttribute(at.getKey(), at.getValue());

          xmlmap.add(element);
        }
      }
      else
        xmlmap = null;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return false;
    }

    return true;
  }

  void afterUnmarshal(Unmarshaller u, Object p)
  {
    org.w3c.dom.Node node;
    org.w3c.dom.NamedNodeMap nodeMap;

    String name;
    Map<String, String> attrs;

    getProperties().clear();

    if (xmlmap != null)
      for (Object xmlNode: xmlmap)
        if (xmlNode instanceof org.w3c.dom.Node)
        {
          node = (org.w3c.dom.Node) xmlNode;
          nodeMap = node.getAttributes();

          name = node.getLocalName();
          attrs = new HashMap<String, String>();

          for (int i = 0, l = nodeMap.getLength(); i < l; i++)
          {
            node = nodeMap.item(i);
            attrs.put(node.getNodeName(), node.getNodeValue());
          }

          getProperties().put(name, attrs);
        }

    xmlmap = null;
  }

  public static void main(String[] args)
    throws Exception
  {
    SchemaBasedProperties props = new SchemaBasedProperties();
    Map<String, String> attrs;

    attrs = new HashMap<String, String>();
    attrs.put("ResId", "A_LABEL");
    props.getProperties().put("LABEL", attrs);

    attrs = new HashMap<String, String>();
    attrs.put("ResId", "A_TOOLTIP");
    props.getProperties().put("TOOLTIP", attrs);

    attrs = new HashMap<String, String>();
    attrs.put("Value", "hide");
    props.getProperties().put("DISPLAYHINT", attrs);

    javax.xml.bind.JAXBContext jc = javax.xml.bind.JAXBContext.newInstance(SchemaBasedProperties.class);

    Marshaller marshaller = jc.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.marshal(props, new java.io.File("test.xml"));

    Unmarshaller unmarshaller = jc.createUnmarshaller();
    props = (SchemaBasedProperties) unmarshaller.unmarshal(new java.io.File("test.xml"));

    System.out.println(props.getProperties());
  }
}

내 결과물 :

<SchemaBasedProperties>
    <LABEL ResId="A_LABEL"/>
    <TOOLTIP ResId="A_TOOLTIP"/>
    <DISPLAYHINT Value="hide"/>
</SchemaBasedProperties>

{LABEL={ResId=A_LABEL}, TOOLTIP={ResId=A_TOOLTIP}, DISPLAYHINT={Value=hide}}

요소 이름 / 값 쌍을 사용할 수 있습니다. 속성이 필요 해요 ... 재미있게 보내세요!


이 질문은 다른 질문과 중복되는 것 같습니다. 여기서 일부 마샬 / 비 마샬 솔루션을 하나의 게시물에 수집했습니다. 여기에서 확인할 수 있습니다 : JAXB를 사용한 동적 태그 이름 .

요컨대 :

  1. 에 대한 컨테이너 클래스를 @xmlAnyElement만들어야합니다.
  2. XmlAdapter함께 쌍에서 사용할 수있는 @XmlJavaTypeAdapter컨테이너 클래스와지도 <> 사이의 변환;

xml-apis-1.0을 사용할 때 다음을 직렬화 및 역 직렬화 할 수 있습니다.

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <map>
        <key>value</key>
        <key2>value2</key2>
    </map>
</root>

이 코드 사용 :

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

@XmlRootElement
class Root {

    public XmlRawData map;

}

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Root root = (Root) unmarshaller.unmarshal(new File("src/input.xml"));

        System.out.println(root.map.getAsMap());

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }
}

class XmlRawData {

    @XmlAnyElement
    public List<Element> elements;

    public void setFromMap(Map<String, String> values) {

        Document document;
        try {
            document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }

        for (Entry<String, String> entry : values.entrySet()) {
            Element mapElement = document.createElement(entry.getKey());
            mapElement.appendChild(document.createTextNode(entry.getValue()));
            elements.add(mapElement);
        }
    }

    public Map<String, String> getAsMap() {
        Map<String, String> map = new HashMap<String, String>();

        for (Element element : elements) {
            if (element.getNodeType() == Node.ELEMENT_NODE) {
                map.put(element.getLocalName(), element.getFirstChild().getNodeValue());
            }
        }

        return map;
    }
}

가장 쉬운 해결책을 찾았습니다.

@XmlElement(name="attribute")
    public String[] getAttributes(){
        return attributes.keySet().toArray(new String[1]);
    }
}

이제 다음과 같은 xml 출력이 생성됩니다.

<attribute>key1<attribute>
...
<attribute>keyN<attribute>

참고 URL : https://stackoverflow.com/questions/3941479/jaxb-how-to-marshall-map-into-keyvalue-key

반응형