Map<String, MyObject> in jaxb - java

I got a simple object defined as follows:
#XmlRootElement(name="container")
public class Container{
#XmlJavaTypeAdapter(MapAdapter.class)
private Map<String, MyObject> myobject;
I am trying to deserialize/serialize it correctly using jaxb.
MyObject is a simple bean with two attributes "street" and "address" as String.
In stackoverflow I found examples on how to use jaxb with Map but in this case I want to use object MyObject.
MapAdapter is defined as
class MapElements {
#XmlAttribute
public String key;
#XmlElement
public MyObject value;
private MapElements() {
} //Required by JAXB
public MapElements(String key, MyObject value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public MyObject getValue() {
return value;
}
public void setValue(MyObject value) {
this.value = value;
}
}
public class MapAdapter extends XmlAdapter<MapElements[], Map<String, MyObject>> {
public MapAdapter() {
}
public MapElements[] marshal(Map<String, MyObject> arg0) throws Exception {
MapElements[] mapElements = new MapElements[arg0.size()];
int i = 0;
for (Map.Entry<String, MyObject> entry : arg0.entrySet()){
mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());
}
return mapElements;
}
public Map<String, MyObject> unmarshal(MapElements[] arg0) throws Exception {
Map<String, MyObject> r = new TreeMap<String, MyObject>();
for (MapElements mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}
but once I try to deserialize the object I got error
487:Can not set java.lang.String field com.company.mypackage.myservice.MapElements.key to [Lcom.company.mypackage.myservice.MapElements;
probably it is not possible to do in jaxb because it is strongly typed.
Thanks

I just remembered that I needed a XMLAdapter when I wanted to un/marshall a Map. But... it seems that this is not required for every server / JAXB implementation and sometimes this is even counterproductive. While the error message was not at all helpful to me, as soon as I removed the #XmlJavaTypeAdapter from the map it started working and it marshalled it as expected. So while this isn't really the answer to solve that message, it at least may help others that start with "I need a XMLJavaTypeAdapter for a Map" in mind, as I did.
So summarized: for some JAXB implementation you do not need the #XmlJavaTypeAdapter-annotation, nor do you need that MapAdapter or MapElements-class. It will just work out of the box.

Related

Mapping fields as key-value pair

When reading a JSON file, i would like to map my class as follows:
public class Effect {
private final String type;
private final Map<String, String> parameters;
public Effect(String type, Map<String, String> parameters) {
this.type = type;
this.parameters = parameters;
}
public String getType() {
return this.type;
}
public Map<String, String> getParameters() {
return this.parameters;
}
}
{
"type": {
"key1": "value1",
"key2": "value2",
}
}
So, the mapped JSON object consists of type as the only key and parameters as its value.
I would like to use #JsonCreator on the constructor, but can't figure out, how to map the fields. Do i need to write a custom deserializer or is there an easier way to map the class like i want?
I wrote a custom deserializer, which does what i want, but there might be an easier way, maybe with annotations alone, which i would like to know:
public class EffectDeserializer extends StdDeserializer<Effect> {
private static final long serialVersionUID = 1L;
public EffectDeserializer() {
super(Effect.class);
}
#Override
public Effect deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
JsonNode node = parser.getCodec().readTree(parser);
Iterator<String> fieldNames = node.fieldNames();
if(fieldNames.hasNext()) {
String type = fieldNames.next();
Map<String, String> parameters = new HashMap<>();
for(Iterator<Entry<String, JsonNode>> fields = node.get(type).fields(); fields.hasNext(); ) {
Entry<String, JsonNode> field = fields.next();
parameters.put(field.getKey(), field.getValue().textValue());
}
return new Effect(type, parameters);
}
return null;
}
}
Another way i found would be adding a JsonCreator (constructor in this case), that takes a Map.Entry<String, Map<String, String> and uses that to initialize the values, like this:
#JsonCreator
public Effect(Map.Entry<String, Map<String, String>> entry) {
this.type = entry.getKey();
this.parameters = entry.getValue();
}
If there's no way to get it done with a "normal" constructor, i will probably end up using this, as it uses Jackson's default mapping for Map.Entry, reducing possible error margin.
Add a static factory method that accepts a Map with a dynamic key:
#JsonCreator
public static Effect create(Map<String, Map<String, String>> map) {
String type = map.keySet().iterator().next();
return new Effect(type, map.get(type));
}
EDIT: Just noticed this is basically an uglier version of your own solution using Map.Entry. I would go with that instead.

Collection of Generic Objects. Auto-Casting? Hidden Casting?

I'm attempting to hold node properties in the following manner (this may be inherently wrong):
public class Property<T> {
protected String key;
protected T value;
public Property(String key, T value) {
this.key = key;
this.value = value;
}
}
public class Node {
protected HashMap<String,Property> properties;
public Node() {
properties = new HashMap<>();
}
However, this has the unfortunate side effect of making my a giant mess of casting. I've been reading everything that might be relevant, but nothing seems to address the essentail problem. Here's my current thought:
public void add(String key, Object value) {
if ( ! (value instanceof Property)) {
value = new Property<>(key, value);
}
properties.put(key, (Property)value);
}
public long get(String key, long x) {
return (long)properties.get(key).value;
}
public long[] get(String key, long[] x) {
return (long[])properties.get(key).value;
}
public String get(String key, String x) {
return (String)properties.get(key).value;
}
// etc
Now this is obviously incrediably stupid, but I'm going around in circles trying to simply be able to grab a node property by key, and be assured of it's type based on the key.
It's that simple. A given key must correspond to a given type, both for adding and for getting.
I honestly feel like I'm misunderstanding something fundamental about the nature of Java.
Try this
// a node representing things of type T
public class Node<T> {
protected HashMap<String,Property<T>> properties;
public Node() {
properties = new HashMap<>();
}
// add a T to the map
public void add(String key, T value) {
properties.put(key, new Property<T>(string, value));
}
}
What concerns me about your example, so far, is that a "Node" looks quite a lot like a hashmap Entry. A better question would be "what are you REALLY trying to do"?
A given key must correspond to a given type, both for adding and for getting.
Assuming you mean for the String key to be an identifier for the element and its type, then you're out of luck, it's simply impossible with generics. One option is to define a custom class with appropriately typed field/getters for each known property.
If you mean for the x parameter, then you can use generics to do something like
public <T> T get(String key, T x) {
return (T) properties.get(key).value;
}
but this sets you up for all sorts of ClassCastException. Your compiler should warn against this. (Note, also, that you won't be able to use primitive types directly.)
As the node class can hold properties of any value type, you need to make an unchecked cast anyways. There's no need to overload the get function, you can cast to whatever the expected return type is:
#SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) properties.get(key).value;
}
Examples:
Node node = new Node();
node.add("x", 123);
node.add("y", "ABC");
node.add("z", new Date());
int valueX = node.get("x"); // cast to integer and autobox to int
String valueY = node.get("y"); // cast to String
Date valueZ = node.get("z"); // cast to Date
String valueFail = node.get("z"); // this will throw a ClassCastException
It is possible to write an externally typesafe implementation of this, though it'll require some internal casting that the compiler can't prove is correct.
class TypeSafeMap {
public static final class Key<T> {
// deliberately empty; we're knowingly using reference equality
}
private final Map<Key<?>, Object> map;
TypeSafeMap() {
this.map = new HashMap<>();
}
public <T> T get(Key<T> key) {
return (T) map.get(key); // cast is safe, but the compiler can't prove it
}
public <T> void put(Key<T> key, T value) {
map.put(key, value);
}
}
class SomewhereElse {
static final Key<Integer> myIntKey = new Key<Integer>();
static final Key<String> myStringKey = new Key<String>();
public void doWhatever(TypeSafeMap myMap) {
int myInt = myMap.get(myIntKey);
String myString = myMap.get(myStringKey);
}
}
...That said, if you know the entire set of keys in advance, you can (and should) make a custom class with appropriately typed fields, rather than trying to squeeze the whole thing into a map-like structure.
public class Node
{
public static void main (String[] args)
{
Node node = new Node();
node.addProperty("a", 12L);
node.addProperty("b", "i'm a string");
long number = node.getProperty("a");
String string = node.getProperty("b");
}
private Map<String, Object> properties = new HashMap<>();
public void addProperty(String key, Object value){
this.properties.put(key, value);
}
public <T> T getProperty(String key){
return (T) this.properties.get(key);
}
}
The OP is trying to handle a collection of different objects, so generics aren't the way forward. What he's trying to do is have type-safe processing of each specific object within a collection. Here's how you could do that using the visitor pattern.
// Implement this interface in something which needs to process
// an item from the collection in a way specific to the type of that item
interface Visitor {
void visit(Circle c);
void visit(Square s);
}
class Collection {
Map<String, Shape> shapes = new HashMap<>();
void add(String key, Shape shape) {
shapes.put(key, shape);
}
// when you want to process what's behind a key, send in a visitor
void visit(String key, Visitor visitor) {
// ask the shape to be visited by the visitor
shapes.get(key).visit(visitor);
}
}
interface Shape {
void visit(Visitor visitor);
}
class Circle implements Shape {
void visit(Visitor visitor) {
// tells the visitor to treat this object as a circle
visitor.visit(this);
}
}
Let's say you wanted something which draws a particular shape from the collection.
class DrawingVisitor implements Visitor {
void visit(Circle c) {
// use properties only a circle has to draw it
graphics2d.ellipse(c.getRadius(), c.getCenterPoint());
}
void visit(Square s) {
graphics2d.rectangle(s.getTopLeft(), s.getBottomRight());
}
}
Etc
Make sense?

Dictionary of Objects where key is an instance field in Java

I was wondering if it was possible to have a Java dictionary of objects where one of the fields of the object is defined to be the key of the dictionary.
To be more specific, here's what I would like: I have defined a class with three fields. One of these fields is an Integer and is unique to each object. I would like this field to be the key of the dictionary.
Yes, of course it's possible.
Example :
Map<Integer,MyClass> map = new HashMap<Integer,MyClass>();
MyClass myObject = new MyClass(...);
map.put (myObject.getIntegerKey(), myObject);
If you want to hide the details:
public interface HasOwnKey<K> {
public K getKey();
}
public class MyMap<K, V extends HasOwnKey<K>> {
{
private Map<K,V> map = new HashMap<>();
public V put(V value) {
{
return this.map.put(value.getKey(),value);
}
public V get(K key) {
return this.map.get(key)
}
... etc
}
public class MyClass extends HasOwnKey<String> {
...
#Override String getKey() { return this.key; }
}
MyMap<String, MyClass> myMap = new MyMap<>();
MyClass obj = new MyClass();
obj.setKey("abc");
myMap.put(obj);
Unfortunately Java 7 doesn't seem to be smart enough to infer K from a declaration like
public class MyMap<V extends HasOwnKey<K>> {
so you have to provide the Key type in two places and cannot do
MyMap<MyClass> myMap = new MyMap<>();
You can do that easily as follows :
public class CustomClass
{
private int primaryKey;
private int secondaryField;
private int tertiaryField;
public CustomClass(int primaryKey, int secondaryField, int tertiaryField)
{
this.primaryKey = primaryKey;
this.secondaryField = secondaryField;
this.tertiaryField = tertiaryField;
}
public int getPrimaryKey(CustomClass object)
{
return object.primaryKey;
}
}
public class Test
{
public static void main(String[] args)
{
CustomClass object = new CustomClass(10, 20, 30);
Map map = new HashMap<Integer,CustomClass>();
map.put(object.getPrimaryKey(object), object);
}
}
You may also want to consider using Enums for doing the same, if the number of such records is fairly less, as they provide more readability.
If you already have created a List of those objects you can use an aggregate operation in java 8 like this:
Map<Integer, List<MyClass>> theMap = list
.stream()
.collect( Collectors.groupingBy(MyClass::myIntegerKey) );

How to create bean edit model for Map in Tapestry?

I'm having a Map<String, Boolean> which I'd like to edit values from via BeanEditor.
I've been poking around and figured I need to create my own property conduit for it:
public class MapPropertyConduit implements PropertyConduit {
private Object key;
#SuppressWarnings("rawtypes")
private Class dataType;
#SuppressWarnings("rawtypes")
public MapPropertyConduit(Object key, Class dataType) {
this.key = key;
this.dataType = dataType;
}
#Override
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
return null;
}
#SuppressWarnings("rawtypes")
#Override
public Object get(Object instance) {
return ((Map) instance).get(key);
}
#SuppressWarnings("unchecked")
#Override
public void set(Object instance, Object value) {
((Map) instance).put(key, value);
}
#SuppressWarnings("rawtypes")
#Override
public Class getPropertyType() {
return dataType;
}
}
I initialized it like this:
this.reportingModel = beanModelSource.createEditModel(Map.class, messages);
MViewTransactionDisplayModel selectedModel = getMViewTransactionReportModel();
Map<String, Boolean> displayFields = selectedModel.getDisplayModel();
for (Entry<String, Boolean> entry : displayFields.entrySet()) {
this.reportingModel.add(entry.getKey(), new MapPropertyConduit(entry.getKey(), Boolean.class)).dataType(Boolean.class.getName());
}
In my tml I did this:
<t:beaneditor t:id="mapEditorId" t:object="myMap" t:model="reportingModel"/>
And the exception I get is:
Render queue error in BeginRender[MyPage:mapeditorid.propertyeditor]: Unable to locate a block to edit property 'property1' (with data type 'java.lang.Boolean') of object {property1=true, property2=true, property3=true, property4=true, property5=true, property6=true, property7=true, property8=true, property9=true, property10=true, property11=true, property12=true, property13=true}: There is no defined way to edit data of type 'java.lang.Boolean'. Make a contribution to the BeanBlockSource service for this type.
I am kind of puzzled since I was under the impression that I can edit Booleans with a simple checkbox.
It's either that, or I failed on providing/calling custom property conduit?
Is there a way to fix this so I can freely edit the values in a map?
When I changed
this.reportingModel.add(entry.getKey(), new MapPropertyConduit(entry.getKey(), Boolean.class)).dataType(Boolean.class.getName());
to
this.reportingModel.add(entry.getKey(), new MapPropertyConduit(entry.getKey(), Boolean.class)).dataType("boolean");
it suddenly worked.
Does anyone have a complete list of available data types?
There's an example of using a MapPropertyConduit here
You might also be interested in the map: binding prefix here

Bind multiple elements to map with attribute as key with java XML annotations, JAXB

I have an XML source from which I unmarshall Objects with JAXB.
The XML source:
<album>
<name>something</name>
<id>003030</id>
<artist>someone</artist>
...
</album>
The java source is like (with the required getter/setters as well):
#XmlRootElement(name="album")
class Album {
String name;
Long id;
String artist;
...
}
So far so good. Now I get some image urls in different sizes within album list:
...
<image size="small">http://.../small.jpg</image>
<image size="medium">http://.../medium.jpg</image>
<image size="large">http://.../large.jpg</image>
...
I want to map it to a java Map something like this:
Map<String,String> imageUrls;
Where the map's key would be the size attribute and the map's value would be the element value.
If it's possible, how should I annotate this variable?
helper class Pair
#XmlAccessorType(XmlAccessType.FIELD)
public class Pair {
#XmlAttribute
private String key;
#XmlValue
private String value;
public Pair() {
}
public Pair(String key, String value) {
this.key = key;
this.value = value;
}
//... getters, setters
}
List of pairs
#XmlAccessorType(XmlAccessType.FIELD)
public class PairList
{
private List<Pair> values = new ArrayList<Pair>();
public PairList() {
}
//...
}
adaptor
public class MapAdaptor extends XmlAdapter<PairList, Map<String, String>>
{
#Override
public Map<String, String> unmarshal(PairList list) throws Exception
{
Map<String, String> retVal = new HashMap<String, String>();
for (Pair keyValue : list.getValues())
{
retVal.put(keyValue.getKey(), keyValue.getValue());
}
return retVal;
}
#Override
public PairList marshal(Map<String, String> map) throws Exception
{
PairList retVal = new PairList();
for (String key : map.keySet())
{
retVal.getValues().add(new Pair(key, map.get(key)));
}
return retVal;
}
}
usage in your entity
#XmlJavaTypeAdapter(value = MapAdaptor.class)
private Map<String, String> imageUrls = new HashMap<String, String>();
PS
You can do it without class PairList using Pair[] instead of PairList
adaptor
public class MapAdaptor extends XmlAdapter<Pair[], Map<String, String>>
{
#Override
public Map<String, String> unmarshal(Pair[] list) throws Exception
{
Map<String, String> retVal = new HashMap<String, String>();
for (Pair keyValue : Arrays.asList(list))
{
retVal.put(keyValue.getKey(), keyValue.getValue());
}
return retVal;
}
#Override
public Pair[] marshal(Map<String, String> map) throws Exception
{
List<Pair> retVal = new ArrayList<Pair>();
for (String key : map.keySet())
{
retVal.add(new Pair(key, map.get(key)));
}
return retVal.toArray(new Pair[]{});
}
}
but in this case you can't control name of every pair. It will be item and you can't change it
<item key="key2">valu2</item>
<item key="key1">valu1</item>
PS2
If you will try use List<Pair> instead of PairList, you will get Exception
ERROR: java.util.List haven't no-arg constructor

Categories

Resources