XStream: problems with Generic LinkedHashMap - java

I am using XStream 1.4.8 and trying to serialize a generic LinkedHashMap<?,?>.
LinkedHashMap does not appear to maintain its order when serialized by XStream. I need to write a new Converter for it so that it does.
The problem is exacerbated by the fact that I have several different types of the Generic class LinkedHashMap in use, and I would like to need only one Converter that works for the generic version.
In other words:
given an a arbitrary Object to serialize that may contain several different kinds of fields of type LinkedHashMap<?,?>, how do you marshal and unmarshal them all, with the correct Types used for each of the generics, and with the order maintained in each of the LinkedHashMaps?
This question is similar, but it's not for Generics, and is also based on an older version of XStream:
Xstream does not maintain order of child elements while unmarshalling

You can get some ideas in this source code:
https://github.com/bluesoft-rnd/aperte-workflow-core/blob/master/core/activiti-context/src/main/java/org/aperteworkflow/ext/activiti/ActivitiStepAction.java#L47
Map params = new HashMap();
if (this.params != null) {
String xml = (String) this.params.getValue(execution);
if (xml != null) {
XStream xs = new XStream();
xs.alias("map", java.util.Map.class);
xs.registerConverter(new Converter() {
public boolean canConvert(Class clazz) {
return AbstractMap.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
AbstractMap<String, String> map = (AbstractMap<String, String>) value;
for (Map.Entry<String, String> entry : map.entrySet()) {
writer.startNode(entry.getKey().toString());
writer.setValue(entry.getValue().toString());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, String> map = new HashMap<String, String>();
while (reader.hasMoreChildren()) {
reader.moveDown();
map.put(reader.getNodeName(), reader.getValue());
reader.moveUp();
}
return map;
}
});
params = (Map) xs.fromXML(xml);
}
}

Some examples here:
/**
* Checks whether the XStream-based REST converters should be used for
* rendering the response of this REST request.
* <p>
* This method checks whether the request has a query attribute named
* <tt>"rest"</tt>, or whether there is a system-wide
* {#link BundleProperties#getProperty(String) bundle/system property}
* named <tt>"skalli.rest"</tt> with a value <i>different</i> from <tt>"v1"</tt>.
* In that case, e.g. the request has a query attribute <tt>"rest=v2"</tt>,
* the method returns <code>false</code> to indicate that the new
* RestWriter-based converters should be employed. Otherwise the
* method returns <code>true</code>.
* <p>
* If the requested media type is different from <tt>"text/xml"</tt>,
* always <code>false</code> will be returned.
*
* #return <code>true</code>, if XStream-based converters should be used
* for rendering the response of this REST request.
*/
#SuppressWarnings("nls")
protected boolean enforceOldStyleConverters() {
if (!context.isXML()) {
return false;
}
String restVersion = getQueryAttribute("rest");
if (StringUtils.isBlank(restVersion)) {
restVersion = BundleProperties.getProperty("skalli.rest");
}
if (StringUtils.isNotBlank(restVersion)) {
return "v1".equalsIgnoreCase(restVersion);
}
return true;
}
Full source code here:
http://code.openhub.net/file?fid=FMrVl1G9kYhg416Lk5dachOp98c&cid=br-mQGOdySQ&s=XStream%3A%20problems%20with%20Generic%20LinkedHashMap&pp=0&fl=Java&ff=1&filterChecked=true&fp=390342&mp,=1&ml=1&me=1&md=1&projSelected=true#L0

After some trial and error, I found the solution. For anyone interested, here it is:
It turns out that classes being generic are not actually as much of a problem as I feared. Ironically, Java's compile-time interpretation, which is usually annoying,ends up being to our benefit here: It actually suffices to create a LinkedHashMap with only Object as the generic types.
However, one does need to take care to ensure that the objects stored in the map are unmarshalled as the correct class. This can be done easily enough by just storing the class information for each entry of the map. Note that it does not suffice to store the class information only once for the entire map, since some entries of the map may be subclasses of that class.
private static class LinkedHashMapConverter implements Converter {
#SuppressWarnings("rawtypes")
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(LinkedHashMap.class);
}
#Override
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
#SuppressWarnings("unchecked")
LinkedHashMap<Object, Object> map = (LinkedHashMap<Object, Object>) value;
// store each entry
for (Entry<Object, Object> a : map.entrySet()) {
writer.startNode("entry");
// store the key, the value, and the types of both
writer.startNode("keyClass");
context.convertAnother(a.getKey().getClass());
writer.endNode();
writer.startNode("key");
context.convertAnother(a.getKey());
writer.endNode();
writer.startNode("valueClass");
context.convertAnother(a.getValue().getClass());
writer.endNode();
writer.startNode("value");
context.convertAnother(a.getValue());
writer.endNode();
writer.endNode();
}
}
#SuppressWarnings("rawtypes")
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
LinkedHashMap<Object, Object> res = new LinkedHashMap<Object, Object>();
while (reader.hasMoreChildren()) {
reader.moveDown();
// load the key, the value, and the types of both
reader.moveDown();
Class keyClass = (Class) context.convertAnother(res, Class.class);
reader.moveUp();
reader.moveDown();
Object key = context.convertAnother(res, keyClass);
reader.moveUp();
reader.moveDown();
Class valueClass = (Class) context.convertAnother(res, Class.class);
reader.moveUp();
reader.moveDown();
Object value = context.convertAnother(res, valueClass);
reader.moveUp();
res.put(key, value);
reader.moveUp();
}
return res;
}
}

Related

Java Gson to Json Conversion

I have a class with the following attributes,
public AnalyticsEventProperty(String eventID, String key, Object value, EventPropertyValueType valueType) {
this.eventID = eventID;
this.key = key;
this.value = value;
this.type = valueType();
}
This object is created and passed to an array of event properties, when I do the Json Conversion I get the output below:
{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","name":"app_start","propertyArrayList":[{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","key":"session_id","value":"69200430-95a0-4e14-9a36-67942917573d"}
I am getting 'key and 'value' used, I can see why, but how do I use the key and values as key and values i.e. "session_id":"69200430-95a0-4e14-9a36-67942917573d", bearing in mind that these key and values may have different property names depending on what is passed in the constructor.
When i create the String i am simply calling
String text_to_send = new Gson().toJson(events);
Where events is the ArrayList.
You can solve this by writing a custom TypeAdapterFactory which obtains the default adapter for your class (that is the reflection based one) and uses it to create an in-memory JSON representation in the form of a JsonObject. That JsonObject can then be modified to have the structure you expect; afterwards it has to be written to the JsonWriter:
class RewritingEventPropertyAdapterFactory implements TypeAdapterFactory {
public static final RewritingEventPropertyAdapterFactory INSTANCE = new RewritingEventPropertyAdapterFactory();
private RewritingEventPropertyAdapterFactory() {}
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only consider AnalyticsEventProperty or subtypes
if (!AnalyticsEventProperty.class.isAssignableFrom(type.getRawType())) {
return null;
}
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
TypeAdapter<JsonObject> jsonObjectAdapter = gson.getAdapter(JsonObject.class);
return new TypeAdapter<T>() {
#Override
public T read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Deserialization is not supported");
}
#Override
public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
// Remove "key" and "value"
String eventKey = jsonObject.remove("key").getAsString();
JsonElement eventValue = jsonObject.remove("value");
// Add back an entry in the form of `"key": "value"`
jsonObject.add(eventKey, eventValue);
// Write the transformed JsonObject
jsonObjectAdapter.write(out, jsonObject);
}
};
}
}
You then have to register the factory with a GsonBuilder.
An alternative would be to perform the complete serialization of the class manually by directly writing the properties to the JsonWriter. This will most likely be a bit more performant, but is also more error-prone.

Key-value map of each value of an enum in Java

Task
In a Java back-end project, I have some (100+) external enums that I can't edit and I need to output them to our front-end. I wanted to output them in a JSON-object like manner. Each enum has different properties name.
e.g. for the following enum
public enum Colors {
RED(1, "RED", "ff0000", Boolean.TRUE),
GREEN(2, "GREEN", "00ff00", Boolean.FALSE),
BLUE(3, "BLUE", "0000ff", Boolean.TRUE);
private int code;
private String label;
private String hexCode;
private boolean isAwesome;
// ... getters and other methods
}
i want to output
[
{
label: "RED"
hexCode: "ff0000"
isAwesome: true
},
{
label: "GREEN"
hexCode: "00ff00"
isAwesome: false
},
...
]
My attempt
I am new to Java, this is the first time I used reflection and I didn't really study anything before going into this. Probably there are some major problems with this code (like performance or some other weird stuff that I don't know), but it compiles and does the job. I don't know if this is safe, so I ask if there are some better ways to do this.
private <T> List<HashMap<String, Object>> enumInserter(Class<T> clazz, List<String> properties) {
return valuesToMap(clazz.getEnumConstants(), parserFactory(clazz, properties));
}
/**
*
* #param <T> type of the enum class
* #param values enumConstants of the enum
* #param parser a function that take a single enumValue of type <T> and returns
* an property-value map
* #return the array of the property-value maps of each value
*/
private <T> List<HashMap<String, Object>> valuesToMap(T[] values, Function<T, HashMap<String, Object>> parser) {
List<HashMap<String, Object>> enumValues = new ArrayList<>();
for (T enumValue : values) {
HashMap<String, Object> processedValue = parser.apply(enumValue);
enumValues.add(processedValue);
}
return enumValues;
}
/**
*
* #param <T> the type of the enum class
* #param clazz the enum class
* #param properties the properties to be added in the map
* #return a parser function that take a single enumValue of type <T> as input and
* returns a property-value map of the given enumValue
*/
private <T> Function<T, HashMap<String, Object>> parserFactory(Class<T> clazz, List<String> properties) {
return ((T enumValue) -> {
HashMap<String, Object> map = new HashMap<>();
properties.stream().forEach(propertyName -> {
String methodName = getterFromProperty(propertyName);
try {
Method method = clazz.getMethod(methodName);
Object methodResult = method.invoke(enumValue);
map.put(propertyName, methodResult);
} catch (Exception e) {
// ... error logging
}
});
return map;
});
}
/**
* Return the "standard" property getter of a property. e.g. "example" will
* return "getExample"
*
* #param property
* #return property getter method name
*/
private String getterFromProperty(String property) {
return "get" + property.substring(0, 1).toUpperCase() + property.substring(1);
}
The usual approach to this is either through use of annotation #JsonFormat(shape = JsonFormat.Shape.OBJECT) or through usage of a custom tailored serializer.
Lets say that you are referencing the enum from class A
class A {
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
private Colors color;
}
this will make the color to be serialized the way you want.
Alternative aproach would to register a custom serializer for your Enum this you can do the following way:
public class ColorSerializer extends StdSerializer {
public ColorSerializer() {
super(Color.class);
}
public ColorSerializer(Class t) {
super(t);
}
public void serialize(Color color, JsonGenerator generator,
SerializerProvider provider)
throws IOException, JsonProcessingException {
generator.writeStartObject();
generator.writeFieldName("code");
generator.writeString(color.getCode());
generator.writeFieldName("hexCode");
generator.writeString(color.getHexcode());
generator.writeFieldName("isAwsome");
generator.writeNumber(color.isAwsome());
generator.writeEndObject();
}
}
Since your enums are external you can always place them in wrappers which are internal to your project and this way control their serialization process.
If you want to serialize them using the same strategy you can place your reflection code in the serializer. This way you will get a single generic serializer, instead of writing for each enum.
This is how you can register the custom serializer:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Color.class, new ColorSerializer ());
mapper.registerModule(module);

Storing specific data types in hashmap

I have to use a map which stores keys of type Integer, String and Long only.
One solution: To store type Object and in put method check with instanceof operator. Is there any better solution, maybe with enum
You can use a map and storing Long as String into it
or you can use two different hashmap and duplicate put/get methods. If you have two types, it is probably for two different things, and having two different map should probably be the correct answer
Create a class that has a map as a member and add methods that will store and retrieve int and long as Strings.
class MyMap {
private Map mabObject = Map<String, Object>;
public void add(long key, Object value) {
mapObject.put(Long.toString(key),value);
}
public void add(String key, Object value) {
mapObject.put(key, value);
}
public Object get(long key) {
return mapObject.get(Long.toString(key));
}
public Object get(String key) {
return mapObject.get(key);
}
}
I agree with Paul Boddington's comment, and the need of such trick shows that code smells.
Just for a funny excercise (not for production code) I've made an example that shows what we can do in compile time for limiting types of keys in a map.
For example we can create a wrapper allowing only values of specific classes.
common/map/Wrap.java
package common.map;
import java.util.Arrays;
import java.util.List;
public class Wrap<T> {
private T value;
private Wrap(T value){
this.value = value;
}
public T get() {
return this.value;
}
/*
* it's important to implement this method
* if we intend to use Wrap instances as map's key
*
* and it's needed to see that hash codes are computing differently in different classes,
* and depending on `allowedClasses` contents we can face some unexpected collisions
* so if you care of performance - test your maps usage accurately
*/
public int hashCode() {
return this.value.hashCode();
}
/*
* static
*/
private static List<Class> allowedClasses = Arrays.asList(Long.class, String.class);
public static <T> Wrap<T> create(Class<? extends T> clazz, T value) {
if (!allowedClasses.contains(clazz)) {
throw new IllegalArgumentException("Unexpected class " + clazz);
}
return new Wrap<>(value);
}
public static <T> Wrap<T> create(AllowedClasses allowedClass, T value) {
return create(allowedClass.clazz, value);
}
public enum AllowedClasses {
LONG(Long.class),
STRING(String.class);
private Class clazz;
AllowedClasses(Class clazz) {
this.clazz = clazz;
}
}
}
And let's run it
common/map/Example.java
package common.map;
import common.map.Wrap.AllowedClasses;
import java.util.HashMap;
import java.util.Map;
public class Example {
public static void main(String... args) {
Map<Wrap, Object> map = new HashMap<>();
// next two lines create wrappers for values of types we added to enum AllowedClasses
// but since enums cannot have type parameters, we are not able to check
// if the second parameter type is compatible with a type associated with given enum value
// so I think usage of enum is useless for your purpose
Wrap<?> valLong0 = Wrap.create(AllowedClasses.LONG, "the string in place of Long is OK");
Wrap<?> valString0 = Wrap.create(AllowedClasses.STRING, 12345);
// from the next lines you can see how we can use the Wrap class to keep
// only allowed types to be associated with the map keys
Wrap<Long> valLong = Wrap.create(Long.class, 1L); // legal
Wrap<String> valString = Wrap.create(String.class, "abc"); // legal
Wrap<String> valWrong = Wrap.create(String.class, 123); // doesn't compile
Wrap<Object> valWrong2 = Wrap.create(Object.class, 123); // compiles but throws exception in runtime
Object obj = ThirdParty.getObjectOfUnknownClass();
Wrap<?> valDynamic = Wrap.create(obj.getClass(), obj); // compiles but MAYBE throws exception in runtime
// so we get to this point only if all the wrappers are legal,
// and we can add them as keys to the map
map.put(valLong, new Object());
map.put(valString, new Object());
map.put(valDynamic, new Object());
}
}
HashMap<DataType1,DataType2>hm = new HashMap<DataType1,DataType2>();
or
Map<DataType1,DataType2> m = new HashMap<DataType1,DataType2>();
m.put(key, value);
Instead of DataType1 & DataType2 you can add Integer,String,Long ,etc. and use the put(key,value) method to enter key and values into the HashMap.

Converting XML to Java Map<String, Integer> using XStream

I'm converting XML code to a Java Map. The XML matches a large number of random words with a number (a probability distribution) and looks like this:
<?xml version="1.0" encoding="UTF-8" ?>
<root>
<Durapipe type="int">1</Durapipe>
<EXPLAIN type="int">2</EXPLAIN>
<woods type="int">2</woods>
<hanging type="int">3</hanging>
<hastily type="int">2</hastily>
<localized type="int">1</localized>
.......
</root>
I'm trying to implement this with XStream. Here's the Java code that my main program currently uses:
XStream xstream = new XStream();
Map<String, Integer> englishCorpusProbDist;
xstream.registerConverter(new MapEntryConverter());
englishCorpusProbDist = (Map<String, Integer>)xstream.fromXML(new File("C:/Users/David Naber/Documents/IREP Project/frequencies.xml"));
And here's my MapEntryConverterClass:
public class MapEntryConverter implements Converter {
public boolean canConvert(Class clazz) {
return Map.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
Map<String, Integer> map = (Map<String, Integer>) value;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
writer.startNode(entry.getKey().toString());
writer.setValue(entry.getValue().toString());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, Integer> map = new HashMap<String, Integer>();
while (reader.hasMoreChildren()) {
reader.moveDown();
map.put(reader.getNodeName(), reader.getValue());
reader.moveUp();
}
return map;
}
}
I"m getting an error in the above function, on the line "map.put(reader.getNodeName(), reader.getValue());". The error says: "The method put(String, Integer) in the type Map is not applicable for the arguments (String, String)."
So I really have two questions here. First of all, why is this error happening and how can I fix it? Secondly, what more will I need to implement to finally get XStream to convert this to XML?
Any help is much appreciated. Thank you in advance!
Yes error is correct reader.getValue() is giving String , You must have to Type Cast it in Integer
Change below code
map.put(reader.getNodeName(), reader.getValue());
to
map.put(reader.getNodeName(), new Integer(reader.getValue()));
This is my example for more complex data with nested Maps
public static class MapEntryConverter implements Converter {
static final Converter INSTANCE = new MapEntryConverter();
public boolean canConvert(Class clazz) {
return Map.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
Map map = (Map) value;
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
writer.startNode(entry.getKey().toString());
Object val = entry.getValue();
if (val != null) context.convertAnother(val);
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
while (reader.hasMoreChildren()) {
reader.moveDown();
String key = reader.getNodeName();
Object value = null;
if (reader.hasMoreChildren()) {
value = unmarshal(reader, context);
} else {
value = reader.getValue();
}
map.put(key, value);
reader.moveUp();
}
return map;
}
}
Have fun!

Dictionary-like data structure. Is this a good practice?

I need a data structure to store different type of objects.E.g. String, Boolean and other classes.
Is using a Map<String, Object> where using the key you get the according object which assumes that you know how to cast it a good practice?
Is there a better solution?
That's a perfect use case for a PropretyHolder I wrote a while ago. You can read in length about it on my blog. I developed it with immutability in mind, feel free to adapt it to your needs.
In general I'd say if you want to profit from type safety in Java you need to know your keys. What I mean by that - it will be hardly possible to develop type safe solution where keys come from external source.
Here's a special key that knows type of its value (it's not complete please download the source for complete version):
public class PropertyKey<T> {
private final Class<T> clazz;
private final String name;
public PropertyKey(Class<T> valueType, String name) {
this.clazz = valueType;
this.name = name;
}
public boolean checkType(Object value) {
if (null == value) {
return true;
}
return this.clazz.isAssignableFrom(value.getClass());
}
... rest of the class
}
Then you develop a data structure that utilizes it:
public class PropertyHolder {
private final ImmutableMap<PropertyKey<?>, ?> storage;
/**
* Returns value for the key of the type extending-the-one-declared-in-the {#link PropertyKey}.
*
* #param key {#link PropertyKey} instance.
* #return Value of the type declared in the key.
*/
#SuppressWarnings("unchecked")
public <T extends Serializable> T get(PropertyKey<T> key) {
return (T) storage.get(key);
}
/**
* Adds key/value pair to the state and returns new
* {#link PropertyHolder} with this state.
*
* #param key {#link PropertyKey} instance.
* #param value Value of type specified in {#link PropertyKey}.
* #return New {#link PropertyHolder} with updated state.
*/
public <T> PropertyHolder put(PropertyKey<T> key, T value) {
Preconditions.checkNotNull(key, "PropertyKey cannot be null");
Preconditions.checkNotNull(value, "Value for key %s is null",
key);
Preconditions.checkArgument(key.checkType(value),
"Property \"%s\" was given "
+ "value of a wrong type \"%s\"", key, value);
// Creates ImmutableMap.Builder with new key/value pair.
return new PropertyHolder(filterOutKey(key)
.put(key, value).build());
}
/**
* Returns {#link Builder} with all the elements from the state except for the given ket.
*
* #param key The key to remove.
* #return {#link Builder} for further processing.
*/
private <T> Builder<PropertyKey<? extends Serializable>, Serializable> filterOutKey(PropertyKey<T> key) {
Builder<PropertyKey<? extends Serializable>, Serializable> builder = ImmutableMap
.<PropertyKey<? extends Serializable>, Serializable> builder();
for (Entry<PropertyKey<? extends Serializable>, Serializable> entry : this.storage.entrySet()) {
if (!entry.getKey().equals(key)) {
builder.put(entry);
}
}
return builder;
}
... rest of the class
}
I omit here a lot of unnecessary details please let me know if something is not clear.
A typesafe heterogeneous container can be used for this purpose:
import java.util.HashMap;
import java.util.Map;
public class Container {
private Map<Class<?>, Object> container = new HashMap<Class<?>, Object>();
public <T> void putElement(Class<T> type, T instance) {
if (type == null) {
throw new NullPointerException("Type is null");
}
//container.put(type, instance); // 'v1'
container.put(type, type.cast(instance)); // 'v2' runtime type safety!
}
public <T> T getElement(Class<T> type) {
return type.cast(container.get(type));
}
public static void main(String[] args) {
Container myCont = new Container();
myCont.putElement(String.class, "aaa");
myCont.putElement(Boolean.class, true);
myCont.putElement(String[].class, new String[] {"one", "two"});
System.out.println(myCont.getElement(String.class));
System.out.println(myCont.getElement(String[].class)[1]);
}
}
Limitation: this container in its form is capable only to store one instance/object type.
In putElement() you can achieve runtime type safety by using a dynamic cast. This will hoewever add an extra overhead.
E.g: Try to pass a raw class object to the container. Note where the exception occurs:
Class raw = Class.forName("MyClass");
myCont.putElement(raw, "aaa"); //ClassCastException if using 'v2'
System.out.println(myCont.getElement(raw)); //ClassCastException if using 'v1'

Categories