I have a Java Backend responding rest request with response with this class:
import java.util.Collection;
import java.util.Collections;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.codehaus.jackson.map.annotate.JsonSerialize;
#XmlRootElement
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class RestResponse<T> {
#XmlElement(name = "METADATA")
private JsonMetadata jsonMetadata;
private Collection<T> result;
public RestResponse() {
jsonMetadata = new JsonMetadata();
}
public RestResponse(JsonMetadata metadata) {
this.jsonMetadata = metadata;
}
public JsonMetadata getJsonMetadata() {
return jsonMetadata;
}
public void setJsonMetadata(JsonMetadata jsonMetadata) {
this.jsonMetadata = jsonMetadata;
}
public Collection<T> getResult() {
return result;
}
public void setResult(Collection<T> result) {
this.result = result;
}
public void setObjectList(Collection<T> objectList) {
if (objectList != null) {
this.result = objectList;
}
}
public void setObject(T object) {
if (object != null) {
setObjectList(Collections.singletonList(object));
}
}
public void setErrorMessage(String msg) {
jsonMetadata.setErrorMessage(msg);
}
public void setWarnMessage(String msg) {
jsonMetadata.setWarnMessage(msg);
}
}
And works ok sending something like this:
METADATA: {STATUS: "0", ERROR_MESSAGE: ""}
result: [{id: "4010", name: "Demo"}]
Now I'm trying to use Apache Syncope and want to use maven artifact like read hear:
https://syncope.apache.org/docs/reference-guide.html#client-library
but when I add this lines:
<dependency>
<groupId>org.apache.syncope.client</groupId>
<artifactId>syncope-client-lib</artifactId>
<version>2.1.2</version>
</dependency>
To the pom.xml in my proyect in Eclipse. Only add this lines, Do not do anything else, and then the rest response changes to:
jsonMetadata: {status: "0", errorMessage: ""}
result: [{id: "4010", name: "Demo"}]
For me is a problem because I manage the errors whit this 'METADATA' word.
Does anyone know why this change occurs?
In this case you define #XmlElement(name = "METADATA") only in for the first one field JsonMetadata. Remember Java only get this annotation to the first field under it!
When i create xml i prefer to use the notation in their get method, for example:
#XmlRootElement(name = "root")
public class RestResponse<T> {
#XmlElement(name = "metadata")
public JsonMetadata getJsonMetadata() {
return jsonMetadata;
}
public void setJsonMetadata(JsonMetadata jsonMetadata) {
this.jsonMetadata = jsonMetadata;
}
public void setResult(<any> result) {
this.result = result;
}
#XmlElement(name="result")
public <any> getResult() {
return result;
}
REMEMBER: you have to create both setter and getter for each field! with the correct name (I use netbeans ide and it suggest automatically to add this method with the correct name).
BUT there is another solution...
#XmlAccessorType(XmlAccessType.FIELD)
public class RestResponse<T> {
#XmlElement(name = "METADATA")
private JsonMetadata jsonMetadata;
private Collection<T> result;
//...
with this notation before the class you shuld risolve your problem.
So there are 2 way:
-add the method (i prefer this one)
-add this notatio (witout add or touch anything, i don't like because the method are more usefull)
it is not possible to use the two solutions together!
Related
When serialising objects to XML and specifying namespaces for properties using
#JacksonXmlRootElement(namespace = "http://...")
Jackson will append or prepend ´wstxns1´ to the namespace. For example, say we have these classes:
VtexSkuAttributeValues.java
#JacksonXmlRootElement(localName = "listStockKeepingUnitName")
public class VtexSkuAttributeValues {
#JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
#JacksonXmlElementWrapper(useWrapping = false)
private VtexSkuAttributeValue[] stockKeepingUnitFieldNameDTO;
public VtexSkuAttributeValue[] getStockKeepingUnitFieldNameDTO() {
return stockKeepingUnitFieldNameDTO;
}
public void setValues(VtexSkuAttributeValue[] values) {
this.stockKeepingUnitFieldNameDTO = values;
}
}
VtexSkuAttributeValue.java
#JacksonXmlRootElement(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
public class VtexSkuAttributeValue {
private String fieldName;
private FieldValues fieldValues;
private int idSku;
public int getIdSku() {
return idSku;
}
public String getFieldName() {
return fieldName;
}
public FieldValues getFieldValues() {
return fieldValues;
}
public void setIdSku(int idSku) {
this.idSku = idSku;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public void setFieldValues(FieldValues fieldValues) {
this.fieldValues = fieldValues;
}
#JacksonXmlRootElement(localName = "fieldValues", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
public static class FieldValues {
#JacksonXmlProperty(namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")
#JacksonXmlElementWrapper(useWrapping = false)
public String[] string;
public String[] getString() {
return string;
}
public void setValues(String[] values) {
this.string = values;
}
}
}
I then use the XmlMapper to serialise and get:
<listStockKeepingUnitName>
<wstxns1:StockKeepingUnitFieldNameDTO xmlns:wstxns1="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>talle</fieldName>
<fieldValues>
<wstxns2:string xmlns:wstxns2="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</wstxns2:string>
</fieldValues>
<idSku>258645</idSku>
</wstxns1:StockKeepingUnitFieldNameDTO>
<wstxns3:StockKeepingUnitFieldNameDTO xmlns:wstxns3="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>color</fieldName>
<fieldValues>
<wstxns4:string xmlns:wstxns4="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6244</wstxns4:string>
</fieldValues>
<idSku>258645</idSku>
</wstxns3:StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>
Even though this is valid XML, the web service I'm working with doesn't accept it. I debugged it and it's due to the wstxns properties in the tags that Jackson adds for some reason.
Is there a way to prevent Jackson from adding that to the tags. The only workaround I could come up with is performing a string.replaceAll on the resulting XML but it's obviously not ideal.
To write XML Jackson uses javax.xml.stream.XMLStreamWriter. You can configure instance of that class and define your own prefixes for namespaces and set default one if needed. To do that we need to extend com.fasterxml.jackson.dataformat.xml.XmlFactory class and override a method which creates XMLStreamWriter instance. Example implementation could look like below:
class NamespaceXmlFactory extends XmlFactory {
private final String defaultNamespace;
private final Map<String, String> prefix2Namespace;
public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
}
#Override
protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
XMLStreamWriter writer = super._createXmlWriter(ctxt, w);
try {
writer.setDefaultNamespace(defaultNamespace);
for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
writer.setPrefix(e.getKey(), e.getValue());
}
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, null);
}
return writer;
}
}
You can use it as below:
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
public class XmlMapperApp {
public static void main(String[] args) throws Exception {
String defaultNamespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";
Map<String, String> otherNamespaces = Collections.singletonMap("a", "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
XmlMapper xmlMapper = new XmlMapper(new NamespaceXmlFactory(defaultNamespace, otherNamespaces));
xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(xmlMapper.writeValueAsString(new VtexSkuAttributeValues()));
}
}
In VtexSkuAttributeValues class you can declare:
public static final String DEF_NMS = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";
and use it for every class and field where it should be used as default namespace. For example:
#JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = DEF_NMS)
For properties, for which you do not want to change name you can use:
#JacksonXmlProperty(namespace = VtexSkuAttributeValues.DEF_NMS)
Above code prints for some random data:
<listStockKeepingUnitName>
<StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>Name1</fieldName>
<fieldValues>
<a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
</fieldValues>
<idSku>123</idSku>
</StockKeepingUnitFieldNameDTO>
<StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>Name1</fieldName>
<fieldValues>
<a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
</fieldValues>
<idSku>123</idSku>
</StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>
If it is not what you want you can play with that code and try other methods which are available for you to configure this instance.
To create this example Jackson in version 2.9.9 was used.
This seems to be the missing piece. It allows you to set the prefix and namespace.
static class NamespaceXmlFactory extends XmlFactory {
private final String defaultNamespace;
private final Map<String, String> prefix2Namespace;
public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
}
#Override
protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
XMLStreamWriter2 writer = (XMLStreamWriter2)super._createXmlWriter(ctxt, w);
try {
writer.setDefaultNamespace(defaultNamespace);
writer.setPrefix("xsi", "http://www.w3.org/2001/XMLSchema-instance");
for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
writer.setPrefix(e.getKey(), e.getValue());
}
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, null);
}
return writer;
}
}
The only remaining issue I have is
#JacksonXmlProperty(localName = "#xsi.type", isAttribute = true, namespace = "http://www.w3.org/2001/XMLSchema-instance")
#JsonProperty("#xsi.type")
private String type;
Creates the following output:
Still trying to resolve how to make it be xsi:type="networkObjectGroupDTO" instead.
I am using https://mongodb.github.io/mongo-java-driver-reactivestreams/1.11/. It seems to be using https://mongodb.github.io/mongo-java-driver/3.10/. I have a bunch of other registered classes that are working fine. I am using the suggestions at https://mongodb.github.io/mongo-java-driver/3.5/bson/pojos/ (and Save List of interface objects using mongo driver for java) for dealing with fields that have interfaces. However, I get the below error. For other classes for which I get this error, I can simply add an empty constructor to the class, but I cannot do so for an interface. Any help would be appreciated.
Caused by: org.bson.codecs.configuration.CodecConfigurationException: Failed to decode 'SearchCriteria'. Decoding 'filters' errored with: Cannot find a public constructor for 'FilterInterface'.
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:222)
at org.bson.codecs.pojo.PojoCodecImpl.decodeProperties(PojoCodecImpl.java:197)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:121)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:125)
at org.bson.codecs.pojo.LazyPojoCodec.decode(LazyPojoCodec.java:57)
at org.bson.codecs.DecoderContext.decodeWithChildContext(DecoderContext.java:93)
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:213)
... 36 common frames omitted
Caused by: org.bson.codecs.configuration.CodecConfigurationException: Cannot find a public constructor for 'FilterInterface'.
at org.bson.codecs.pojo.CreatorExecutable.checkHasAnExecutable(CreatorExecutable.java:140)
at org.bson.codecs.pojo.CreatorExecutable.getInstance(CreatorExecutable.java:107)
at org.bson.codecs.pojo.InstanceCreatorImpl.<init>(InstanceCreatorImpl.java:40)
at org.bson.codecs.pojo.InstanceCreatorFactoryImpl.create(InstanceCreatorFactoryImpl.java:28)
at org.bson.codecs.pojo.ClassModel.getInstanceCreator(ClassModel.java:71)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:120)
at org.bson.codecs.pojo.PojoCodecImpl.decode(PojoCodecImpl.java:125)
at org.bson.codecs.pojo.CollectionPropertyCodecProvider$CollectionCodec.decode(CollectionPropertyCodecProvider.java:74)
at org.bson.codecs.pojo.CollectionPropertyCodecProvider$CollectionCodec.decode(CollectionPropertyCodecProvider.java:43)
at org.bson.codecs.DecoderContext.decodeWithChildContext(DecoderContext.java:93)
at org.bson.codecs.pojo.PojoCodecImpl.decodePropertyModel(PojoCodecImpl.java:213)
... 42 common frames omitted
Below are snippets of my code:
#BsonDiscriminator
public interface FilterInterface<T> {
boolean applyOn(T value);
T getValue();
...
}
public abstract class Filter<T> implements FilterInterface<T> {
public Filter() { }
public abstract boolean applyOn(T value);
public abstract T getValue();
...
}
public class AddressFilter extends Filter<Address> {
public AddressFilter() { }
public boolean applyOn(Address value) {
return true;
}
public Address getValue() {
return new Address();
}
...
}
public class SearchCriteria {
public SearchCriteria() { }
private List<FilterInterface> filters;
}
public static void init() {
String url = <hidden>;
MongoClient mongoClient = MongoClients.create(new ConnectionString(url));
// For POJOs here
// For interface classes.
PojoCodecProvider pojoCodecProvider = PojoCodecProvider.builder()
.conventions(ImmutableList.of(CLASS_AND_PROPERTY_CONVENTION, ANNOTATION_CONVENTION))
.register(SearchCriteria.class)
.register(
ClassModel.builder(FilterInterface.class).enableDiscriminator(true).build(),
ClassModel.builder(Filter.class).enableDiscriminator(true).build(),
ClassModel.builder(AddressFilter.class).enableDiscriminator(true).build())
.automatic(true)
.build();
CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(pojoCodecProvider));
String dbName = <hidden>;
mongoDb = mongoClient.getDatabase(dbName).withCodecRegistry(codecRegistry);
}
The example provided in the link works perfectly fine. Much credit goes to that user for this answer.
You have probably inserted the records when FilterInterface was a class or before using the discriminators.
Solution:
Dropping the collection and re-populating will work smoothly.
If it's production scenario, you might wanna add the field _t manually to each document.
Tip: Always use the same code for serialization and deserialization.
Explanation:
Referring to the documentation of the c-sharp driver.
The default discriminator conventions both use an element named _t to store the discriminator value in the BSON document.
If you have inserted the documents before enabling the discriminators, there would be no field _t in the document. When the driver starts decoding, it won't find and fallback to default decoder for the interface FilterInterface.
On the other hand, if you have inserted the documents when FilterInterface was a class, the value of _t will be the fully qualified name of the class. When the decoder starts to decode, it will get the ClassModel and try to create an instance of FilterInterface. Since it is now an interface, the decoder won't find the constructor.
Here is some additional info: you can change the field _t to any other name and you can specify the discriminator value by using over the classes.
#BsonDiscriminator(key = "<field_id>", value = "<value>")
Here is the modified version of the example of that answer. Please run it with discriminators disabled and then run it with discriminators enabled. You will face the same error as yours. Then clean the collection and then try again.
package org.bson.codecs.chng;
import com.google.common.collect.Lists;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.ClassModel;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.bson.conversions.Bson;
import java.util.Arrays;
import java.util.List;
public class MongoInterfaceTest {
private static MongoClient mongoClient;
static {
init();
}
public static void init() {
try {
ClassModel<User> userClassModel = ClassModel.builder(User.class).enableDiscriminator(false).build();
ClassModel<JavaUser> javaUserClassModel = ClassModel.builder(JavaUser.class).enableDiscriminator(false).build();
ClassModel<PythonUser> pythonUserClassModel = ClassModel.builder(PythonUser.class).enableDiscriminator(false).build();
ClassModel<TestUser> testUserClassModel = ClassModel.builder(TestUser.class).enableDiscriminator(false).build();
CodecRegistry pojoCodecRegistry = CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(
PojoCodecProvider.builder()
.register(
userClassModel,
javaUserClassModel,
pythonUserClassModel,
testUserClassModel
)
.build()
)
);
mongoClient = MongoClients.create(
MongoClientSettings.builder()
.codecRegistry(pojoCodecRegistry)
.applyConnectionString(new ConnectionString(ApplictaionConfig.MONGODB_URL))
.applyToConnectionPoolSettings(builder -> {
builder.minSize(10);
})
.build()
);
} catch (Exception e) {
System.out.println("Connection mongodb failed");
throw new RuntimeException();
}
}
public static void main(String[] args) {
MongoCollection<TestUser> collection = getMongoCollection("TestUser", TestUser.class);
JavaUser javaUser = new JavaUser<Integer>("a");
PythonUser pythonUser = new PythonUser<String>("b", "1");
TestUser testUser = new TestUser(javaUser.name, javaUser);
insertOne(collection, testUser);
testUser = new TestUser(pythonUser.name, pythonUser);
insertOne(collection, testUser);
Bson bson = Filters.and(Filters.eq("name", "a"));
TestUser testUser1 = findFirst(collection, bson);
System.out.println(testUser1);
testUser1.users.forEach(x -> System.out.println(x.dev()));
bson = Filters.and(Filters.eq("name", "b"));
testUser1 = findFirst(collection, bson);
System.out.println(testUser1);
testUser1.users.forEach(x -> System.out.println(x.dev()));
}
/**
* 获得collection对象
*/
public static <T> MongoCollection<T> getMongoCollection(String collectionName, Class<T> tClass) {
MongoDatabase mongoDatabase = mongoClient.getDatabase("kikuu");
MongoCollection<T> collection = mongoDatabase.getCollection(collectionName, tClass);
return collection;
}
public static <T> void insertOne(MongoCollection<T> collection, T document) {
insertMany(collection, Lists.newArrayList(document));
}
public static <T> void insertMany(MongoCollection<T> collection, List<T> documents) {
collection.insertMany(documents);
}
public static <T> T findFirst(MongoCollection<T> collection) {
return (T) collection.find().first();
}
public static <T> T findFirst(MongoCollection<T> collection, Bson bson) {
return (T) collection.find(bson).first();
}
public static interface User<T> {
String dev();
T foo();
}
public static class JavaUser<T> implements User<T> {
public String name;
public JavaUser() {
}
public JavaUser(String name) {
this.name = name;
}
#Override
public String dev() {
return "java";
}
#Override
public String toString() {
return "JavaUser{" +
"name='" + name + '\'' +
'}';
}
#Override
public T foo() {
return null;
}
}
public static class PythonUser<T> implements User<T> {
public String name;
public String age;
public PythonUser() {
}
public PythonUser(String name, String age) {
this.name = name;
this.age = age;
}
#Override
public String dev() {
return "python";
}
#Override
public String toString() {
return "PythonUser{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
#Override
public T foo() {
return null;
}
}
public static class TestUser {
public String name;
public List<User> users;
public TestUser() {
}
public TestUser(String name, User... users) {
this.name = name;
this.users = Arrays.asList(users);
}
#Override
public String toString() {
return "TestUser{" +
"name='" + name + '\'' +
", user=" + users +
'}';
}
}
}
I have a class like this:
public class SampleDto {
private String normalProperty1;
private String normalProperty2;
private String normalProperty3;
private String sensitiveProperty1;
private String sensitiveProperty2;
public String getNormalProperty1() {
return normalProperty1;
}
public void setNormalProperty1(String normalProperty1) {
this.normalProperty1 = normalProperty1;
}
public String getNormalProperty2() {
return normalProperty2;
}
public void setNormalProperty2(String normalProperty2) {
this.normalProperty2 = normalProperty2;
}
public String getNormalProperty3() {
return normalProperty3;
}
public void setNormalProperty3(String normalProperty3) {
this.normalProperty3 = normalProperty3;
}
public String getSensitiveProperty1() {
return sensitiveProperty1;
}
public void setSensitiveProperty1(String sensitiveProperty1) {
this.sensitiveProperty1 = sensitiveProperty1;
}
public String getSensitiveProperty2() {
return sensitiveProperty2;
}
public void setSensitiveProperty2(String sensitiveProperty2) {
this.sensitiveProperty2 = sensitiveProperty2;
}
}
There are parts in the application where i need to serialize it as it is because the object is in a secure environment.
But i need to store the json in a db and store it without the sensitiveProperties, I can't just ignore the properties because they are needed in the other processes.
I was thinking to use Jackson views to solve the problem but i don't know if there is something special in Jackson where I can say, every json object that has the property "sensitiveProperty1" set it to null.
I'm using Java and Jackson
I think that this site covers what you're looking for pretty well.
Essentially what you'll want to do is to add #JsonIgnoreProperties(value = { "intValue" }) at the class level or #JsonIgnore at the field level and then Jackson should take care of the rest for you.
In your case that would look something like:
public class SampleDto {
#JsonIgnore
private String normalProperty1;
private String normalProperty2;
...
I am writing a spring websocket application with StompJS on the client side.
On the client side I am intending to send a List of objects and on the server side when it is mapping into java object, it converts itself into a LinkedHashMap
My client side code is
function stomball() {
stompClient.send("/brkr/call", {}, JSON.stringify(listIds));
}
Listids looks like
[{
"path": "/a/b/c.txt",
"id": 12
}, {
"path": "/a/b/c/d.txt",
"id": 13
}]
List Id object looks like
public class ListId {
private String path;
private Long id;
//getters and setters...
}
The Controller looks like this
#MessageMapping("/call" )
#SendTo("/topic/showResult")
public RetObj process(List<ListId> listIds) {
if (!listIds.isEmpty()) {
for(ListId listId: listIds) {
}
}
So I get a java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.blah.ListId
However when I do the same with normal Spring Controller with RestMapping it works fine, Is there anything with springs MessageMapping annotation that maps objects to java differently than the traditional way
I am not sure why is not casting to ListID
I changed it from a List to an Array and it works! Here is what I did
#MessageMapping("/call" )
#SendTo("/topic/showResult")
public RetObj process(ListId[] listIds) {
if (!listIds.isEmpty()) {
for(ListId listId: listIds) {
}
}
Thanks to this question ClassCastException: RestTemplate returning List<LinkedHashMap> instead of List<MymodelClass>
I know this question has already been answered but here's another solution.
To get Jackson to convert your JSON array to list you'll have to wrap it in another object and serialize/deserialize that object.
So you'll have to send following JSON to server
{
list: [
{
"path": "/a/b/c.txt",
"id": 12
}, {
"path": "/a/b/c/d.txt",
"id": 13
}
]
}
List is wrapped into a another object.
Following is the wrapper class
class ServiceRequest {
private List<ListId> list;
public List<ListId> getList() {
if (list == null) {
list = new ArrayList<ListId>();
}
return list;
}
}
and the message method will become
#MessageMapping("/call" )
#SendTo("/topic/showResult")
public RetObj process(ServiceRequest request) {
List<ListId> listIds = request.getList();
if (!listIds.isEmpty()) {
for(ListId listId: listIds) {
}
}
}
Test Code
import java.util.ArrayList;
import java.util.List;
import org.codehaus.jackson.map.ObjectMapper;
public class TestJackson {
public static void main(String[] args) throws Exception {
System.out.println("Started");
String json = "{\"list\":[{\"path\":\"/a/b/c.txt\",\"id\":12},{\"path\":\"/a/b/c/d.txt\",\"id\":13}]}";
ObjectMapper mapper = new ObjectMapper();
ServiceRequest response = mapper.readValue(json.getBytes("UTF-8"), ServiceRequest.class);
for(ListId listId : response.getList()) {
System.out.println(listId.getId() + " : " + listId.getPath());
}
}
public static class ServiceRequest {
private List<ListId> list;
public List<ListId> getList() {
if (list == null) {
list = new ArrayList<ListId>();
}
return list;
}
}
public static class ListId {
private String path;
private String id;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
}
Test Output
Started
12 : /a/b/c.txt
13 : /a/b/c/d.txt
I have these classes:
#XStreamAlias("person")
public class PersonConfig {
private AnimalConfig animalConfig;
}
public interface AnimalConfig {}
#XStreamAlias("dog");
public class DogConfig extend AnimalConfig {}
#XStreamAlias("cat");
public class CatConfig extend AnimalConfig {}
And I would like to be able to deserialize this xml with the classes above:
<person>
<dog/>
<person>
As well as deserialize this xml too, with the same classes:
<person>
<cat/>
<person>
So that in both cases, the PersonConfig's field animalConfig is filled. In the first XML with a DogConfig instance and in the second XML with a CatConfig instance.
Is this possible by adding some annotation to make this work?
It seems XStream does not allow you to do it easily.
Your question is similar to this one, asking for managing something like a xsd:choice with XStream.
If you don't necessarily need to use XStream, JAXB will allow you to do it easily :
#XmlRootElement(name="person")
public class PersonConfig {
private AnimalConfig animalConfig;
#XmlElementRefs({
#XmlElementRef(name="cat", type=CatConfig.class),
#XmlElementRef(name="dog", type=DogConfig.class)
})
public AnimalConfig getAnimalConfig() {
return animalConfig;
}
public void setAnimalConfig(AnimalConfig animalConfig) {
this.animalConfig = animalConfig;
}
}
After some researches, listing all available classes for your property can be avoided if you choose to use the XmlAdapter.
In Blaise Doughan link, the example uses an abstract class, not an interface.
Edit :
As Blaise Doughan said in its comment, #XmlElementRef is better suited for this purpose. Code has been updated accordingly.
You can write a converter.
public class CustomConverter implements Converter {
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
// TODO: Get annotation value from object 'source' with name of tag via Reflection.
// Or add a method to the AnimalConfig interface giving you tag name to put to serialization output.
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
// TODO: use reflection to create animal object based on what you xml tag you have at hahd.
return context.convertAnother(context.currentObject(), SomeAnimalClazz.class);
}
public boolean canConvert(Class type) {
return type.equals(AnimalConfig.class);
}
}
There's a disadvantage: polymorphism will require you to use Java Reflection API and performance degradation.
This is quite easy. You just have to do it right and not like my previous speakers. When you process the annotations, XStream can assign those classes.
#XStreamAlias("person")
public class PersonConfig {
private AnimalConfig animalConfig;
public String toXml() {
XStream xstream = new XStream();
xstream.processAnnotations(DogConfig.class);
xstream.processAnnotations(CatConfig.class);
return xstream.toXML(this);
}
}
public interface AnimalConfig {}
#XStreamAlias("dog");
public class DogConfig implements AnimalConfig {}
#XStreamAlias("cat");
public class CatConfig implements AnimalConfig {}
It works out of the box, with out any annotations...
private static interface Test {
String getName();
Params getParams();
}
private static interface Params {
}
private static class OneParams implements Params {
private String oneValue;
public String getOneValue() {
return oneValue;
}
public void setOneValue(String oneValue) {
this.oneValue = oneValue;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("OneParams [oneValue=");
builder.append(oneValue);
builder.append("]");
return builder.toString();
}
}
private static class TwoParams implements Params {
private String twoValue;
public String getTwoValue() {
return twoValue;
}
public void setTwoValue(String twoValue) {
this.twoValue = twoValue;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("TwoParams [twoValue=");
builder.append(twoValue);
builder.append("]");
return builder.toString();
}
}
private static class OneTest implements Test {
private String name;
private Params params;
#Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public Params getParams() {
return params;
}
public void setParams(Params params) {
this.params = params;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("OneTest [name=");
builder.append(name);
builder.append(", params=");
builder.append(params);
builder.append("]");
return builder.toString();
}
}
---- now deserialize like this...
System.out
.println(ser
.deserialize("<XStreamTest_-OneTest><name>OneTest</name><params class=\"XStreamTest$OneParams\"><oneValue>1</oneValue></params></XStreamTest_-OneTest>"));
System.out
.println(ser
.deserialize("<XStreamTest_-OneTest><name>TwoTest</name><params class=\"XStreamTest$TwoParams\"><twoValue>2</twoValue></params></XStreamTest_-OneTest>"));