JAXB Eclipse MOXy Nullable value and retrieve only

2019-08-09 21:52发布

问题:

I have another more tricky question:

E.g. Person Class has: --String firstName --String lastName --Map stringMap

   Person person = new Person ();
   person.setFirstName("FirstName");
   person.setLastName("LastName");
   Map<String,String> stringMap = new HashMap<String,String>();
   stringMap.put("IwantThisKeyInXml","IwantThisValueInXml");
   stringMap.put("IDONTwantThisKeyInXml","IDONTwantThisValueInXml");


   InputStream iStream1 = classLoader.getResourceAsStream("person-binding.xml");
   List<Object> fileList = new ArrayList<Object>();
            fileList.add(iStream1);
   Map<String, Object> properties = new HashMap<String,Object>();               

   properties.put(JAXBContextProperties.OXM_METADATA_SOURCE,  fileList);
   JAXBContext ctx = JAXBContext.newInstance(new Class[] { GaDictionary.class, Person.class }, properties);

   Marshaller marshaller = ctx.createMarshaller();
   marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

   Writer output = new StringWriter();
   marshaller.marshal(person, output);
   System.out.println(output);

person-binding.xml is below

   <?xml version="1.0"?>
   <xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="my.test.model"  xml-mapping-metadata-complete="true">
    <xml-schema
        element-form-default="QUALIFIED"/>
    <java-types>
        <java-type name="Person"  xml-accessor-type="NONE">
            <xml-root-element/>
            <xml-type prop-order="firstName lastName"/>
            <java-attributes>
                <xml-element java-attribute="firstName" name="first-name"/>
                <xml-element java-attribute="lastName" name="last-name"/>
                <xml-element java-attribute="stringMap" name="string-map"/>
            </java-attributes>
        </java-type>
      </java-types>
    </xml-bindings>

So accordingly the definition the result is:

   <?xml version="1.0" encoding="UTF-8"?>
   <person>
   <first-name>FirstName</first-name>
   <last-name>LastName</last-name>
   <string-map>
      <entry>
         <key>IwantThisKeyInXml</key>
         <value>IwantThisKeyInXml</value>
      </entry>
      <entry>
         <key>IDONTwantThisKeyInXml</key>
         <value>IDONTwantThisKeyInXml</value>
      </entry>
   </string-map>
   </person>

How Could I exclude that entry on the stringMap? I tried with virtual access method but maybe I wasn't able to configure it properly?! Should I remove that value after marshaling? Which could it be a smart and maintainable way?

Last question is if I have null value for a property. How could I configure the person-binding-xml file (oxm.file) to get the relative xml tag empty?

I could change the model class but I prefer don't add any lines of code if it's possible. (This is the reason why I populated the xml file)

So the result that I would like to obtain is:

 <?xml version="1.0" encoding="UTF-8"?>
   <person>
     <first-name>FirstName</first-name>
     <last-name>LastName</last-name>
     <string-map>
       <entry>
         <key>IwantThisKeyInXml</key>
         <value>IwantThisKeyInXml</value>
       </entry>
      </string-map>
  </person>

For the second related question: I tried nillable="true" in but it doesn't work ! So For istance If I have a null value for a stringMap or firstName or something else I will get respectively and tag closed with any body.

Or another way that I would like to obtain is:

 <?xml version="1.0" encoding="UTF-8"?>
   <person>
     <first-name>FirstName</first-name>
     <last-name>LastName</last-name>
     <string-map>
       <entry>
         <key>IwantThisKeyInXml</key>
         <value>IwantThisKeyInXml</value>
       </entry>
       <entry><!-- I have the entry but I have an empty value-->
         <key>KEY</key>
         <value></value>
       </entry>
      </string-map>
  </person>

Thanks to all

回答1:

In your solution the null-policy is the right idea but it needs to be on the value attribute of the StringEntryType not on the whole map. So your bindings file should include something like this:

 <java-type name="StringEntryType">
        <java-attributes>
            <xml-element java-attribute="value" name="value">
                <xml-null-policy null-representation-for-xml="EMPTY_NODE"/>
            </xml-element>
        </java-attributes>
    </java-type>

Your solution looks good but I thought I would also post an alternate soluction that makes use of MOXy's read-only and write-only attributes that you may prefer. In this example the variable myMap will be set during unmarshal but during marshal the Map returned from getModifiedMap method will be called. In the getModifiedMap we also replace the null values with empty strings so the empty tags will be marshalled.

public class Root {

    public Map<String, String> myMap = new HashMap<String, String>();

    public Map<String, String> getModifiedMap(){
         Map modifiedMap = new HashMap<String, String>();       
         Set<Entry<String, String>> entries = myMap.entrySet();
         Iterator<Entry<String, String>> iter = entries.iterator();
         while(iter.hasNext()){
             Entry<String, String> nextEntry = iter.next();
             String nextKey = nextEntry.getKey();
             if(!nextKey.equals("omitthiskey")){
                 String nextValue = nextEntry.getValue();
                 if(nextValue == null){
                     nextValue ="";
                 }               
                 modifiedMap.put(nextKey, nextValue);
             }
         }
         return modifiedMap;
    }

    public void setModifiedMap(Map<String, String> newMap){
        //this method shouldn't get hit but needs to be present 
    }
}

and the corresponding bindings file would include something like this:

<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" package-name="test2" >
    <xml-schema element-form-default="QUALIFIED"/>  
    <java-types>
    <java-type name="Root"  xml-accessor-type="NONE">
        <xml-root-element/>
        <java-attributes>
            <xml-element java-attribute="myMap" name="myMap" read-only="true" />
            <xml-element java-attribute="modifiedMap" name="myMap" write-only="true"/>                      
        </java-attributes>
    </java-type>                    
    </java-types>
</xml-bindings>


回答2:

If you wanted to prevent certain Map entries from being marshalled to XML, you could provide an XmlAdapter that would remove any unwanted entries at marshal time:

import java.util.Map;

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

public final class MyAdapter extends XmlAdapter<Map<String, String>, Map<String, String>> {

    private final String OMIT = "IDONTwantThisKeyInXml";

    @Override
    public Map<String, String> unmarshal(Map<String, String> arg0) throws Exception {
        return arg0;
    }

    @Override
    public Map<String, String> marshal(Map<String, String> arg0) throws Exception {
        if (arg0 != null) {
            arg0.remove(OMIT);
        }
        return arg0;
    }

}

Not sure I understand your second question, could you explain a little more about what behaviour you'd like to see?

Hope this helps,

Rick



回答3:

I found the right solution:

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

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

        public final class StringMapAdapter extends XmlAdapter<StringAdapterMap, Map<String, String>> {

            private final String OMIT = "birthPlaceString";

            @Override
            public Map<String, String> unmarshal(StringAdapterMap v) throws Exception {
                 HashMap<String, String> hashMap = new HashMap<String, String>();
                 System.out.println("unmarshal"); 
                 for(StringEntryType myEntryType : v.entry) {
                     hashMap.put(myEntryType.key, myEntryType.value);
                  }
                  return hashMap;
            }

            @Override
            public StringAdapterMap marshal(Map<String, String> v) throws Exception {
                StringAdapterMap myMapType = new StringAdapterMap();
              for(Entry<String, String> entry : v.entrySet()) {
                  StringEntryType EntryType = new StringEntryType();
                   EntryType.key= entry.getKey();
                   EntryType.value = entry.getValue();
                   if (!OMIT.equals(EntryType.key))
                       myMapType.entry.add(EntryType);
              }
              return myMapType;

            }

StringAdapterMap class:

        import java.util.ArrayList;
        import java.util.List;

        public class StringAdapterMap {
             public List<StringEntryType> entry = new ArrayList<StringEntryType>();

        }

StringEntryType class:

        public class StringEntryType {

               public String key;

               public String value;

        }

in oxm.file remember to configure the adapter

     <xml-element java-attribute="stringMap" name="string-map" >
      <xml-java-type-adapter value="it.cineca.jaxb.adapter.StringMapAdapter" />    
     </xml-element>