Marshalling List<Object> with MOXy - java

I have read many of Blaise Doughan's StackOverflow answers and blog posts, and I thought I understood his examples for using #XmlAnyElement with Object[]'s.
But the same principles doesn't appear to work with List - as shown with the example below (partly copied from one of his examples, and partly copied from xjc output).
I believe the code below should create JSON:
{"method":"test","status":["value":"500"]}
but instead it is creating the JASON:
{"method":"test","value":["com.mdsh.test.JsonRequestTest$Status#64dbfe37"]}
which is not much use to me.
Can anyone guide me to marshall this small object correctly, please?
package com.mdsh.test;
import static org.junit.Assert.assertEquals;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.junit.Test;
import org.jvnet.jaxb2_commons.lang.JAXBToStringStrategy;
import org.jvnet.jaxb2_commons.lang.ToStringStrategy;
import org.jvnet.jaxb2_commons.locator.ObjectLocator;
public class JsonRequestTest
{
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public static class Request
{
String method;
#XmlAnyElement(lax = true)
protected List<Object> any = new Vector<Object>();
public String getMethod()
{
return this.method;
}
public void setMethod(final String value)
{
this.method = value;
}
public List<Object> getAny()
{
if (this.any == null) {
this.any = new Vector<Object>();
}
return this.any;
}
#Override
public String toString()
{
final ToStringStrategy strategy = JAXBToStringStrategy.INSTANCE;
final StringBuilder buffer = new StringBuilder();
append(null, buffer, strategy);
return buffer.toString();
}
public StringBuilder append(final ObjectLocator locator, final StringBuilder buffer, final ToStringStrategy strategy)
{
strategy.appendStart(locator, this, buffer);
appendFields(locator, buffer, strategy);
strategy.appendEnd(locator, this, buffer);
return buffer;
}
public StringBuilder appendFields(final ObjectLocator locator, final StringBuilder buffer, final ToStringStrategy strategy)
{
{
List<Object> theAny;
theAny = (((this.any!= null)&&(!this.any.isEmpty()))?getAny():null);
strategy.appendField(locator, this, "any", buffer, theAny);
}
return buffer;
}
}
public static class Status
{
int value;
public int getValue()
{
return this.value;
}
public void setValue(final int value)
{
this.value = value;
}
}
#Test
public void testListOfObjects() throws JAXBException
{
System.setProperty(JAXBContext.class.getName(), "org.eclipse.persistence.jaxb.JAXBContextFactory");
final Map<String, Object> props = new HashMap<String, Object>();
props.put(MarshallerProperties.MEDIA_TYPE, "application/json");
props.put(MarshallerProperties.JSON_INCLUDE_ROOT, false);
final JAXBContext ctx = JAXBContext.newInstance(
new Class<?>[] { Request.class },
props);
final Marshaller m = ctx.createMarshaller();
final StringWriter writer = new StringWriter();
final Request req = new Request();
req.setMethod("test");
final Status stat = new Status();
stat.setValue(500);
req.getAny().add(stat);
m.marshal(req, writer);
assertEquals("{\"method\":\"test\",\"status\":[\"value\":\"500\"]}",
writer.toString());
}
}

Related

Jaxb with baseclass doesn't serialize derived elements

I have a list of modules in a tree like structure. The baseclass contains the childs and some generic property, while the derived class should provide it's individual fields. Now when I try to serialize this with JAXB I get only the members of the baseclass but not the ones from the derived classes.
Application.java
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Application
{
public static void main(String[] args)
{
try
{
Emulators emulators = new Emulators();
emulators.addChild(new VICEModule());
emulators.addChild(new VICEModule());
StringWriter sw = new StringWriter();
JAXBContext jaxbContext = JAXBContext.newInstance(emulators.getClass());
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); // Pretty print
jaxbMarshaller.marshal(emulators, sw);
String s = sw.toString();
System.out.println(s);
}
catch(Throwable e)
{
System.err.println("Exception:"+e);
}
}
}
ModuleBase.java
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import com.sun.xml.internal.txw2.annotation.XmlAttribute;
public class ModuleBase
{
private List<ModuleBase> mChilds = new ArrayList<>();
private String mModuleId;
private String mModuleName;
public ModuleBase(String oModuleId, String oModuleName)
{
setModuleId(oModuleId);
setModuleName(oModuleName);
}
public void addChild(ModuleBase oModuleNode)
{
mChilds.add(oModuleNode);
}
#XmlElement(name="ModuleList")
public List<ModuleBase> getChildModules()
{
return mChilds;
}
#XmlAttribute
public String getModuleId()
{
return mModuleId;
}
public void setModuleId(String oModuleId)
{
mModuleId = oModuleId;
}
#XmlAttribute
public String getModuleName()
{
return mModuleName;
}
public void setModuleName(String oModuleName)
{
mModuleName = oModuleName;
}
}
Emulators.java
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "Emulators")
public class Emulators
extends ModuleBase
{
public Emulators()
{
super("IdEmu129872q53", "Emulators");
}
}
VICEModule.java
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
#XmlRootElement(name = "VICEModule")
public class VICEModule
extends ModuleBase
{
private String mInstallationPath;
private int mPort;
public VICEModule()
{
super("Id123456", "Vice");
mPort = 6510;
}
//#XmlElement(name="InstallationPath")
#XmlValue
public String getInstallationPath()
{
return mInstallationPath;
}
public void setInstallationPath(String oPath)
{
mInstallationPath = oPath;
}
//#XmlElement(name="Port")
#XmlValue
public int getPort()
{
return mPort;
}
public void setPort(int nPort)
{
mPort = nPort;
}
}
Now when I serialize it, I get the following XML where the VICEModule values are missing and also the module is listed as ModuleList instead of VICEModule and the base fields are put as tags instead of attributes:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Emulators>
<ModuleList>
<moduleId>Id123456</moduleId>
<moduleName>Vice</moduleName>
</ModuleList>
<ModuleList>
<moduleId>Id123456</moduleId>
<moduleName>Vice</moduleName>
</ModuleList>
<moduleId>IdEmu129872q53</moduleId>
<moduleName>Emulators</moduleName>
</Emulators>
So what this should look like is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Emulators ModuleId="IdEmu129872q53" ModuleName="Emulators">
<VICEModule ModuleId="Id1" ModuleName="Name">
<InstallationPath>Path1</InstallationPath>
<Port>6510</Port>
</VICEModule>
<VICEModule ModuleId="Id2" ModuleName="Name">
<InstallationPath>Path2</InstallationPath>
<Port>6511</Port>
</VICEModule>
</Emulators>
When I use the VICEModule as the baseclass, then the XML looks more like it (still without the attributes though).
Writing this small demo app helped, because now I could more easily play around with the marshalling and finally got it working. I'm posting the changes here, to make this sample complete in case somebody needs it as a reference. Emulators.java was not changed, so it's not listed in the answer.
Short answer:
I had to add #XmlAnyElement(lax=true) to my getter for the child modules in order to convert the resulting XML node into a named node as I want it to. Additionally in the main application I had to traverse the tree and collect all classes that are used, so that I can pass it to the JAXBContext.
Application.java
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Application
{
public static List<Class<?>> createClasses(ModuleBase oModule)
{
List<Class<?>> classes = new ArrayList<>();
classes.add(oModule.getClass());
for(ModuleBase module : oModule.getChildModules())
{
List<Class<?>> cls = createClasses(module);
classes.addAll(cls);
}
return classes;
}
public static void main(String[] args)
{
try
{
Emulators emulators = new Emulators();
emulators.addChild(new VICEModule("Id1", "VICE V1", "V1 Path", 6510));
emulators.addChild(new VICEModule("Id2", "VICE V2", "V2 Path", 6511));
StringWriter sw = new StringWriter();
List<Class<?>> classes = createClasses(emulators);
Class<?>[] cls = new Class<?>[classes.size()];
for(int i = 0; i < classes.size(); i++)
cls[i] = classes.get(i);
JAXBContext jaxbContext = JAXBContext.newInstance(cls);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); // Pretty print
jaxbMarshaller.marshal(emulators, sw);
String s = sw.toString();
System.out.println(s);
}
catch(Throwable e)
{
System.err.println("Exception:"+e);
}
return;
}
}
ModuleBase.java
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlAttribute;
public class ModuleBase
{
private List<ModuleBase> mChilds = new ArrayList<>();
private String mModuleId;
private String mModuleName;
public ModuleBase(String oModuleId, String oModuleName)
{
setModuleId(oModuleId);
setModuleName(oModuleName);
}
public void addChild(ModuleBase oModuleNode)
{
mChilds.add(oModuleNode);
}
#XmlAnyElement(lax=true)
public List<ModuleBase> getChildModules()
{
return mChilds;
}
#XmlAttribute
public String getModuleId()
{
return mModuleId;
}
public void setModuleId(String oModuleId)
{
mModuleId = oModuleId;
}
#XmlAttribute
public String getModuleName()
{
return mModuleName;
}
public void setModuleName(String oModuleName)
{
mModuleName = oModuleName;
}
}
VICEModule.java
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "VICEModule")
public class VICEModule
extends ModuleBase
{
private String mInstallationPath;
private int mPort;
public VICEModule()
{
super("Id123456", "Vice");
mPort = 6510;
}
public VICEModule(String oId, String oName, String oPath, int nPort)
{
super(oId, oName);
setInstallationPath(oPath);
setPort(nPort);
}
#XmlElement(name="InstallationPath")
public String getInstallationPath()
{
return mInstallationPath;
}
public void setInstallationPath(String oPath)
{
mInstallationPath = oPath;
}
#XmlElement(name="Port")
public int getPort()
{
return mPort;
}
public void setPort(int nPort)
{
mPort = nPort;
}
}
Now the XML renders exactly as intended with storing all the objects from derived classes.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Emulators moduleId="IdEmu129872q53" moduleName="Emulators">
<VICEModule moduleId="Id1" moduleName="VICE V1">
<InstallationPath>V1 Path</InstallationPath>
<Port>6510</Port>
</VICEModule>
<VICEModule moduleId="Id2" moduleName="VICE V2">
<InstallationPath>V2 Path</InstallationPath>
<Port>6511</Port>
</VICEModule>
</Emulators>

Executing flush method, for sending mutations to accumulo without closing the writer

I am writing data to accumulo storage natively using Geomesa Native Client. Here is my java code
package org.locationtech.geomesa.api;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.mock.MockInstance;
import org.apache.accumulo.core.client.security.tokens.PasswordToken;
import org.apache.accumulo.core.security.Authorizations;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.junit.Assert;
import org.junit.Test;
import org.locationtech.geomesa.accumulo.data.AccumuloDataStore;
import org.locationtech.geomesa.accumulo.index.AccumuloFeatureIndex;
import org.locationtech.geomesa.accumulo.index.AccumuloFeatureIndex$;
import org.locationtech.geomesa.utils.index.IndexMode$;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.filter.FilterFactory2;
import javax.annotation.Nullable;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
public class WorkerBeta {
public static void main(String[] args){
try {
DomainObjectValueSerializer dovs = new DomainObjectValueSerializer();
final GeoMesaIndex<DomainObject> index = AccumuloGeoMesaIndex.buildWithView(
"aj_v14",
"localhost:2181",
"hps",
"root", "9869547580",
false,
dovs,
new SimpleFeatureView<DomainObject>() {
AttributeTypeBuilder atb = new AttributeTypeBuilder();
private List<AttributeDescriptor> attributeDescriptors =
Lists.newArrayList(atb.binding(Integer.class).buildDescriptor("rId")
, atb.binding(String.class).buildDescriptor("dId")
, atb.binding(Integer.class).buildDescriptor("s")
, atb.binding(Integer.class).buildDescriptor("a")
, atb.binding(Integer.class).buildDescriptor("e")
);
#Override
public void populate(SimpleFeature f, DomainObject domainObject, String id, byte[] payload, Geometry geom, Date dtg) {
f.setAttribute("rId", domainObject.rideId);
f.setAttribute("dId", domainObject.deviceId);
f.setAttribute("s", domainObject.speed);
f.setAttribute("a", domainObject.angle);
f.setAttribute("e", domainObject.error);
}
#Override
public List<AttributeDescriptor> getExtraAttributes() {
return attributeDescriptors;
}
}
);
//Inserting
final DomainObject one = new DomainObject(1, "AJJASsP", 12, 40, 1);
final GeometryFactory gf = JTSFactoryFinder.getGeometryFactory();
System.out.println(index.insert(
one,
gf.createPoint(new Coordinate(-74.0, 34.0)),
date("2017-03-31T01:15:00.000Z")
));
//Read
GeoMesaQuery q = GeoMesaQuery.GeoMesaQueryBuilder.builder()
.within(-90.0, -180, 90, 180)
.during(date("2017-01-01T00:00:00.000Z"), date("2017-04-01T00:00:00.000Z"))
.build();
Iterable<DomainObject> results = index.query(q);
int counter = 0;
for(DomainObject dm : results){
counter += 1;
System.out.println("result counter: " + counter);
dovs.toBytes(dm);
}
}
catch (Exception ex){
ex.printStackTrace();
}
index.close();
}
public static class DomainObject {
public final int rideId;
public final String deviceId;
public final int angle;
public final int speed;
public final int error;
public DomainObject(int rideId, String deviceId, int angle, int speed, int error) {
this.rideId = rideId;
this.deviceId = deviceId;
this.angle = angle;
this.speed = speed;
this.error = error;
}
}
public static class DomainObjectValueSerializer implements ValueSerializer<DomainObject> {
public static final Gson gson = new Gson();
#Override
public byte[] toBytes(DomainObject o) {
return gson.toJson(o).getBytes();
}
#Override
public DomainObject fromBytes(byte[] bytes) {
return gson.fromJson(new String(bytes), DomainObject.class);
}
}
public static Date date(String s) {
return Date.from(ZonedDateTime.parse(s).toInstant());
}
}
The problem with this code is, I need to create index object every time for a new insert request and call index.close() to reflect the same. But I can't execute insert() agin, once index.close() is called. However i will be accepting insert request from queue at very high rate and I don't want to create index object every time. How can i do that?
In short how i can flush writes without calling close().
I created geomesa Client class file to use geomesa natively. Below is the partial implementation of the same which shows how you can flush with AccumuloAppendFeatureWriter without calling to close.
public class GeomesaClient {
private AccumuloDataStore ds = null;
private AccumuloAppendFeatureWriter fw = null;
private SimpleFeatureSource sfs = null;
private String tableName = "";
private FeatureStore fst = null;
private SimpleFeatureType sft;
public GeomesaClient(Map<String, String> dsConf) throws Exception {
this.ds = (AccumuloDataStore) DataStoreFinder.getDataStore(dsConf);
this.tableName = dsConf.get("tableName");
sft = createFeatureType();
if(!Arrays.asList(this.ds.getTypeNames()).contains(sft.getTypeName())){
ds.createSchema(sft);
}
this.fst = (FeatureStore)sfs;
this.fw = (AccumuloAppendFeatureWriter) (this.ds.getFeatureWriterAppend(sft.getTypeName(),
Transaction.AUTO_COMMIT));
this.sfs = ds.getFeatureSource(sft.getTypeName());
}
/*
Flush with AccumuloAppendFeatureWriter
*/
public void flush(boolean force) {
fw.flush();
}
}

Statically defined KeyDeserializer not found but if defined locally everything perfect

I am baffled by how registering a custom KeyDeserializer works.
Here is my code:
Matchday.java
package com.example;
import java.io.Serializable;
import java.util.Objects;
public class Matchday implements Serializable, Comparable<Matchday> {
private static final long serialVersionUID = -8823049187525703664L;
private final int matchdayNumber;
public Matchday(final int matchdayNumber) {
this.matchdayNumber = matchdayNumber;
}
public int getMatchdayNumber() {
return matchdayNumber;
}
#Override
public int compareTo(Matchday o) {
return Integer.compare(matchdayNumber, o.getMatchdayNumber());
}
#Override
public final int hashCode() {
return Objects.hash(matchdayNumber);
}
#Override
public final boolean equals(final Object obj) {
return obj instanceof Matchday && Integer.valueOf(matchdayNumber).equals(((Matchday) obj).matchdayNumber);
}
#Override
public String toString() {
return Integer.toString(matchdayNumber);
}
}
TeamPlayer.java
package com.example;
import java.io.Serializable;
import org.apache.commons.lang3.builder.ToStringBuilder;
public class TeamPlayer implements Serializable {
private static final long serialVersionUID = -6057852081020631549L;
private int id;
private String name;
private String surname;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
#Override
public String toString() {
return new ToStringBuilder(this).append("id", id).append("name", name).append("surname", surname).build()
.toString();
}
}
Now if I define a custom map key deserializer for my class Matchday.java, it works like a charm if I do it like this.
KeyDeserializerTest.java
package com.example;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.SortedMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class KeyDeserializerTest {
public static void main(String[] args) throws IOException {
final ObjectMapper objectMapper = new ObjectMapper();
final SimpleModule mySimpleModule = new SimpleModule("dummy", new Version(0, 0, 0, "dummy", "dummy", "dummy"));
mySimpleModule.addKeyDeserializer(Matchday.class, new KeyDeserializer() {
#Override
public Object deserializeKey(String arg0, DeserializationContext arg1)
throws IOException, JsonProcessingException {
return new Matchday(Integer.valueOf(arg0));
}
});
objectMapper.registerModule(mySimpleModule);
final InputStream inputStream = new ByteArrayInputStream(
"{\"1\":[{\"id\": 1, \"name\": \"Arkadiusz\", \"surname\": \"Malarz\"}]}".getBytes());
SortedMap<Matchday, List<TeamPlayer>> map = objectMapper.readValue(inputStream,
new TypeReference<SortedMap<Matchday, List<TeamPlayer>>>() {
});
System.out.println(map);
}
}
It prints
{1=[com.example.TeamPlayer#3a8624[id=1,name=Arkadiusz,surname=Malarz]]}
But if I define both the object mapper and my deserializer instances as static attributes then I get the following exception!
KeyDeserializerStaticTest.java
package com.example;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.SortedMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class KeyDeserializerStaticTest {
public static final ObjectMapper OBJECT_MAPPER = createObjectMapper();
private static final KeyDeserializer MATCHDAY_KEY_DESERIALIZER = new KeyDeserializer() {
#Override
public Object deserializeKey(String key, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
return new Matchday(Integer.valueOf(key));
}
};
private static ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(createSimpleModule());
return objectMapper;
}
private static Module createSimpleModule() {
SimpleModule simpleModule = new SimpleModule("dummy", new Version(0, 0, 0, "dummy", "dummy", "dummy"));
simpleModule.addKeyDeserializer(Matchday.class, MATCHDAY_KEY_DESERIALIZER);
return simpleModule;
}
public static void main(String[] args) throws IOException {
final InputStream inputStream = new ByteArrayInputStream(
"{\"1\":[{\"id\": 1, \"name\": \"Arkadiusz\", \"surname\": \"Malarz\"}]}".getBytes());
SortedMap<Matchday, List<TeamPlayer>> map = OBJECT_MAPPER.readValue(inputStream,
new TypeReference<SortedMap<Matchday, List<TeamPlayer>>>() {
});
System.out.println(map);
}
}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not find a (Map) Key deserializer for type [simple type, class com.example.Matchday]
at [Source: java.io.ByteArrayInputStream#bbc1e0; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234)
at com.fasterxml.jackson.databind.deser.DeserializerCache._handleUnknownKeyDeserializer(DeserializerCache.java:585)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findKeyDeserializer(DeserializerCache.java:168)
at com.fasterxml.jackson.databind.DeserializationContext.findKeyDeserializer(DeserializationContext.java:499)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.createContextual(MapDeserializer.java:247)
at com.fasterxml.jackson.databind.DeserializationContext.handleSecondaryContextualization(DeserializationContext.java:681)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:481)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3899)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3794)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2915)
at com.example.KeyDeserializerStaticTest.main(KeyDeserializerStaticTest.java:43)
What is wrong here? Semantically there is no difference between the above presented main methods. Is this a feature that is somewhere documented or is it simply a bug in Jackson?
The root problem here was the order of initialization of static variables.
It is
public static final ObjectMapper OBJECT_MAPPER = createObjectMapper();
private static final KeyDeserializer MATCHDAY_KEY_DESERIALIZER = new KeyDeserializer() {
#Override
public Object deserializeKey(String key, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
return new Matchday(Integer.valueOf(key));
}
};
while it should be
private static final KeyDeserializer MATCHDAY_KEY_DESERIALIZER = new KeyDeserializer() {
#Override
public Object deserializeKey(String key, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
return new Matchday(Integer.valueOf(key));
}
};
public static final ObjectMapper OBJECT_MAPPER = createObjectMapper();
This was hard to spot because the method addKeyDeserializer(Class<?>, KeyDeserializer) of the class SimpleModule silently adds null references to an internal key deserializers' map. In my opinion it should throw a NullPointerException upon trying adding a key deserializer reference that is null.
The Jackson code for it looks like this.
First addKeKeyDeserializer(Class<?>, KeyDeserializer)
public SimpleModule addKeyDeserializer(Class<?> type, KeyDeserializer deser)
{
if (_keyDeserializers == null) {
_keyDeserializers = new SimpleKeyDeserializers();
}
_keyDeserializers.addDeserializer(type, deser);
return this;
}
there is no check here whether deser is null.
Then it delegates to addDeserializer(Class, KeyDeserializer) of class SimpleKeyDeserializers.
public SimpleKeyDeserializers addDeserializer(Class<?> forClass, KeyDeserializer deser)
{
if (_classMappings == null) {
_classMappings = new HashMap<ClassKey,KeyDeserializer>();
}
_classMappings.put(new ClassKey(forClass), deser);
return this;
}
Here is the null reference also ignored and silently put into _classMappings map.
Here is the issue I posted on GitHub together with the discussion.

(Java) Transfering an Object to an application through Clipboard

How can I copy a Java object (with a couple of fields) to my clipboard in Java, and what is the best way to store these fields?
I have a simple object with a ΗashΜap for storage.
Also, I created a selection for this object, which it implements both Transferable and ClipboardOwner, as well as a DataFlavor for it.
What I'm trying to do is copy this object and paste it in another application. Keep in mind that I don't have access to the source code of this application, so I have to transfer this object in this particular way. Let's say this application is requiring these following fields:
Info (String)
Text (String in UTF-8 Format)
However, copying and pasting this object to the other application doesn't seem to work.
Below you can see my classes:
MyObject:
import java.util.HashMap;
public class MyObject {
private HashMap<Object, Object> data;
public MyObject()
{
data = new HashMap<Object, Object>();
}
public void setData(Object field, Object value)
{
data.put(field, value);
}
}
MyObjectFlavor:
import java.awt.datatransfer.DataFlavor;
public class MyObjectFlavor extends DataFlavor {
public MyObjectFlavor()
{
super(MyObject.class, null);
}
}
MyObjectSelection:
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class MyObjectSelection implements Transferable, ClipboardOwner {
final static DataFlavor myObjectFlavor = new MyObjectFlavor();
final static DataFlavor[] supportedDataFlavors = { myObjectFlavor };
private MyObject myObject;
public MyObjectSelection(MyObject myObject)
{
this.myObject = myObject;
}
public Object getTransferData(DataFlavor dataFlavor)
throws UnsupportedFlavorException, IOException
{
if (!isDataFlavorSupported(dataFlavor)) {
throw new UnsupportedFlavorException(dataFlavor);
}
// Passing data in a ByteArrayOutputStream.
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
ObjectOutputStream objectStream = new ObjectOutputStream(byteStream);
objectStream.writeObject(myObject);
return byteStream.toByteArray();
}
public DataFlavor[] getTransferDataFlavors()
{
return supportedDataFlavors;
}
public boolean isDataFlavorSupported(DataFlavor dataFlavor)
{
for (int i = 0; i < supportedDataFlavors.length; i++) {
if (supportedDataFlavors[i].equals(dataFlavor)) {
return true;
}
}
return false;
}
public void lostOwnership(Clipboard arg0, Transferable arg1)
{
// Lost ownership.
}
}
Finally, my Main class is:
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class Main {
public static void main(String args[]) throws IOException {
MyObject myObject = new MyObject();
MyObjectSelection selection = new MyObjectSelection(myObject);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard ();
String myUtfString = "This is a String in UTF-8";
myObject.setData("Info", "This is a Java Object.");
myObject.setData("Text", new ByteArrayInputStream(myUtfString.getBytes("UTF-8")));
clipboard.setContents(selection, selection);
}
}
I'm sorry for the long post, but I think providing all classes will help. I hope this question is not specific.
Thanks in advance!

From JSON to XML and back in Java

Converting XML to JSON is quite straight forward. XML Attributes become String values and XML Elements become JSON objects. Naming conventions are stricter for XML than JSON. The way back is more complicated. If working in Java, is there a way to reliably convert between the formats?
When you are dealing with a bean, two libraries make your life easy:
GSon for JSON
JAXB for XML
Using the bean as authoritative format conversion between JSON and XML simple. Use this example as reference:
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
#XmlRootElement(name = "Fruit")
public class Fruit {
public final static String XML_FILE = "fruit.xml";
public final static String JSON_FILE = "fruit.json";
public static Fruit fromJson(InputStream in) {
Gson gson = new GsonBuilder().create();
Fruit result = gson.fromJson(new InputStreamReader(in), Fruit.class);
return result;
}
public static Fruit fromXML(InputStream in) throws Exception {
JAXBContext context = JAXBContext.newInstance(Fruit.class);
Unmarshaller um = context.createUnmarshaller();
return (Fruit) um.unmarshal(in);
}
public static void main(String[] args) throws Exception {
Fruit f = new Fruit("Apple", "Red", "Sweet");
Fruit f2 = new Fruit("Durian", "White", "Don't ask");
System.out.println(f.toXML());
System.out.println(f2.toJSON());
f.saveXML(new FileOutputStream(new File(XML_FILE)));
f2.saveJSON(new FileOutputStream(new File(JSON_FILE)));
Fruit f3 = Fruit.fromXML(new FileInputStream(new File(XML_FILE)));
System.out.println(f3.toJSON());
Fruit f4 = Fruit.fromJson(new FileInputStream(new File(JSON_FILE)));
System.out.println(f4.toXML());
}
private String name;
private String color;
private String taste;
public Fruit() {
// Default constructor
}
public Fruit(final String name, final String color, final String taste) {
this.name = name;
this.color = color;
this.taste = taste;
}
/**
* #return the color
*/
public final String getColor() {
return this.color;
}
/**
* #return the name
*/
public final String getName() {
return this.name;
}
/**
* #return the taste
*/
public final String getTaste() {
return this.taste;
}
public void saveJSON(OutputStream out) throws IOException {
GsonBuilder gb = new GsonBuilder();
gb.setPrettyPrinting();
gb.disableHtmlEscaping();
Gson gson = gb.create();
PrintWriter writer = new PrintWriter(out);
gson.toJson(this, writer);
writer.flush();
writer.close();
}
public void saveXML(OutputStream out) throws Exception {
JAXBContext context = JAXBContext.newInstance(Fruit.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(this, out);
}
/**
* #param color
* the color to set
*/
public final void setColor(String color) {
this.color = color;
}
/**
* #param name
* the name to set
*/
public final void setName(String name) {
this.name = name;
}
/**
* #param taste
* the taste to set
*/
public final void setTaste(String taste) {
this.taste = taste;
}
public String toJSON() throws IOException {
GsonBuilder gb = new GsonBuilder();
gb.setPrettyPrinting();
gb.disableHtmlEscaping();
Gson gson = gb.create();
return gson.toJson(this, Fruit.class);
}
public String toXML() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
JAXBContext context = JAXBContext.newInstance(Fruit.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(this, out);
return out.toString();
}
}
Underscore-java can convert xml to json and back. There are methods U.xmlToJson(xml) and U.jsonToXml(json). I am the maintainer of the project.

Categories

Resources