xStream problems - How to deserialize undeclared attributes - java

I'm using xStream to deserialize XML.
My xml contains a tag:
<Element Name="Test" Value="TestValue" Tag="tag" Text.Color="Red"/>
and class
public class Element {
#XStreamAsAttribute
public String Name;
#XStreamAsAttribute
public String Value;
public Map<String, String> AnyAttr = new HashMap<String, String>();
}
fields Name and Value deserialize correct,
How can I deserialize undeclared fields (Tag, Text.Color) to my map ( Map AnyAttr )?

You have to create custom Converter class. Like this one:
public class ElementConverter implements Converter
{
public boolean canConvert(Class clazz) {
return Element.class == clazz;
}
public void marshal(Object object, HierarchicalStreamWriter hsw, MarshallingContext mc) {
Element e = (Element) object;
hsw.addAttribute("Name", e.Name);
hsw.addAttribute("Value", e.Value);
for (Map.Entry<String, String> entry : e.AnyAttr.entrySet())
{
hsw.addAttribute(entry.getKey(), entry.getValue());
}
}
public Object unmarshal(HierarchicalStreamReader hsr, UnmarshallingContext uc) {
Element e = new Element();
String key;
int count = hsr.getAttributeCount();
for (int i = 0; i < count; i++)
{
key = hsr.getAttributeName(i);
if (key.equals("Name")) e.Name = hsr.getAttribute(i);
else
if (key.equals("Value")) e.Value = hsr.getAttribute(i);
else e.AnyAttr.put(key, hsr.getAttribute(i));
}
return e;
}
}
and then you have to register converter in XStream before you use it:
XStream xstream = new XStream();
xstream.aliasType("Element", Element.class);
xstream.registerConverter(new ElementConverter());

You can write your own Converter. That's the only way, you can't achieve this with simple configuration.

Related

mappingmongoconverter readMap

I am using SpringBoot with MongoDB, I have a document as it shows in the image below.
document in mongodb(image)
And I mapped to a java class:
#Document(collection = "instrument")
public abstract class Instrument extends BaseMongoEntity<String> {
#NotEmpty
private String feedProviderId;
#NotEmpty
private String code;
#NotEmpty
private String name;
private Map<String, Object> instrumentData;
private Map<String, Object> commonInfo;
private List<InstrumentHistoricalData> historicalData;
private List<DelayedInstrumentData> delayedData;
private String market;
// Getters, setters, builders, etc.
}
Of course, the instrumentData field contains lots of data, but for the sake of the argument I just wrote those two in the document showed.
So my problem is that I can`t convert the NOW_PRICE to BigDecimal. I can write it with with no problem, BigDecimal to Decimal128, but not the other way around.
I have configured both reader and writer as show below:
#Configuration
public class MongoConfig {
#Bean
public MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(Arrays.asList(
new BigDecimalDecimal128Converter(),
new Decimal128BigDecimalConverter()
));
}
#WritingConverter
private static class BigDecimalDecimal128Converter implements
Converter<BigDecimal, Decimal128> {
#Override
public Decimal128 convert(#NonNull BigDecimal source) {
return new Decimal128(source);
}
}
#ReadingConverter
private static class Decimal128BigDecimalConverter implements
Converter<Decimal128, BigDecimal> {
#Override
public BigDecimal convert(#NonNull Decimal128 source) {
return source.bigDecimalValue();
}
}
}
So checking the MappingMongoConverter.class, I noted this:
protected Map<Object, Object> readMap(TypeInformation<?> type, Bson bson, ObjectPath path) {
Assert.notNull(bson, "Document must not be null!");
Assert.notNull(path, "Object path must not be null!");
Class<?> mapType = typeMapper.readType(bson, type).getType();
TypeInformation<?> keyType = type.getComponentType();
TypeInformation<?> valueType = type.getMapValueType();
Class<?> rawKeyType = keyType != null ? keyType.getType() : null;
Class<?> rawValueType = valueType != null ? valueType.getType() : null;
Map<String, Object> sourceMap = asMap(bson);
Map<Object, Object> map = CollectionFactory.createMap(mapType, rawKeyType, sourceMap.keySet().size());
if (!DBRef.class.equals(rawValueType) && isCollectionOfDbRefWhereBulkFetchIsPossible(sourceMap.values())) {
bulkReadAndConvertDBRefMapIntoTarget(valueType, rawValueType, sourceMap, map);
return map;
}
for (Entry<String, Object> entry : sourceMap.entrySet()) {
if (typeMapper.isTypeKey(entry.getKey())) {
continue;
}
Object key = potentiallyUnescapeMapKey(entry.getKey());
if (rawKeyType != null && !rawKeyType.isAssignableFrom(key.getClass())) {
key = conversionService.convert(key, rawKeyType);
}
Object value = entry.getValue();
TypeInformation<?> defaultedValueType = valueType != null ? valueType : ClassTypeInformation.OBJECT;
if (value instanceof Document) {
map.put(key, read(defaultedValueType, (Document) value, path));
} else if (value instanceof BasicDBObject) {
map.put(key, read(defaultedValueType, (BasicDBObject) value, path));
} else if (value instanceof DBRef) {
map.put(key, DBRef.class.equals(rawValueType) ? value
: readAndConvertDBRef((DBRef) value, defaultedValueType, ObjectPath.ROOT, rawValueType));
} else if (value instanceof List) {
map.put(key, readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST,
(List<Object>) value, path));
} else {
map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType));
}
}
return map;
}
So it's only asking if the value is instance of Document, BasicDBObject, DBRef or List. Otherwise it assumes the value is already mapped, which is not, because it's a numeric value and that possibility it's not being considered.
Am I missing something? Is there a workoaround for this problem? Thank you!
If you dig down into getPotentiallyConvertedSimpleRead(…) you see that we check the CustomConversionInstance for whether there is a conversion registered. I can only assume there's something wrong in your configuration but there's nothing terribly off I can see. Do you have an example project to share that I can play with?

JSON String to Java String

I have these JSON String:
{
"Results": {
"output1": {
"type": "table",
"value": {
"ColumnNames": ["userId", "documentId", "Scored Labels", "Scored Probabilities"],
"ColumnTypes": ["String", "String", "Boolean", "Double"],
"Values": [["100213199594809000000", "1Ktol-SWvAh8pnHG2O7HdPrfbEVZWX3Vf2YIPYXA_8gI", "False", "0.375048756599426"], ["103097844766994000000", "1jYsTPJH8gaIiATix9x34Ekcj31ifJMkPNb0RmxnuGxs", "True", "0.753859758377075"]]
}
}
}
}
And I want to have only the ColumnNames and the Values. I have tried it with something like this:
Map<String,Object> map = mapper.readValue(filename, Map.class);
String CN = (String) map.get("ColumnNames");
But then I get the following error:
Exception in thread "main" org.codehaus.jackson.JsonParseException: Unexpected character ('A' (code 65)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
at [Source: java.io.StringReader#64232b15; line: 1, column: 2]`
I've worked only few times with JSON. Can anybody help me here?
The best case for me would be something like this, which I've done in another case:
String uId = (String) attr.get("userId");
Is it possible?
So now I've done this:
I try it like this:
public class ClientPOJO {
private String userId;
private String documentId;
public String getuserId() {
return userId;
}
public void setuserId(String userId) {
this.userId = userId;
}
public String getdocumentId() {
return documentId;
}
public void setdocumentId(String documentId) {
this.documentId = documentId;
}
}
and then:
ObjectMapper mapper = new ObjectMapper();
ClientPOJO clientes= mapper.readValue(filename, ClientPOJO.class);
String uid = clientes.getuserId();
But now when I make a Prtinout I'll get the same error like before:
Exception in thread "main" org.codehaus.jackson.JsonParseException: Unexpected character ('A' (code 65)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
at [Source: java.io.StringReader#7a6eb29d; line: 1, column: 2]
Java- Convert JSON string into string / integer / Object
String jsonString = "{"username":"Gajender"}";
org.json.JSONObject jsonObj =new JSONObject(jsonString);
String name = (String) jsonObj.get("username").toString();
Below is an example to illustrate a generic approach to solve your problem ( based on Jackson library). You may like to enhance the solution to meet your all requirements.
Comments inlined.
package com.stackoverflow;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
// Junit class
public class TableDeserExample {
// sample input
String inputJson = "{\n" +
" \"Results\": {\n" +
" \"output1\": {\n" +
" \"type\": \"table\",\n" +
" \"value\": {\n" +
" \"ColumnNames\": [\"userId\", \"documentId\", \"Scored Labels\", \"Scored Probabilities\"],\n" +
" \"ColumnTypes\": [\"String\", \"String\", \"Boolean\", \"Double\"],\n" +
" \"Values\": [[\"100213199594809000000\", \"1Ktol-SWvAh8pnHG2O7HdPrfbEVZWX3Vf2YIPYXA_8gI\", \"False\", \"0.375048756599426\"], [\"103097844766994000000\", \"1jYsTPJH8gaIiATix9x34Ekcj31ifJMkPNb0RmxnuGxs\", \"True\", \"0.753859758377075\"]]\n"
+
" }\n" +
" }\n" +
" }\n" +
"}";
// POJO to map the Json structure. You may want to make it generalize based
// on field "type"
// (https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization)
public static class Result {
private String type;
private TableResult value;
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
public void setValue(TableResult value) {
this.value = value;
}
public TableResult getValue() {
return this.value;
}
}
// Pojo for table result
public static class TableResult {
private List<String> columnNames;
private List<String> columnTypes;
private List<Object[]> values;
#JsonProperty("ColumnNames")
public List<String> getColumnNames() {
return this.columnNames;
}
public void setColumnNames(List<String> columnNames) {
this.columnNames = columnNames;
}
#JsonProperty("ColumnTypes")
public List<String> getColumnTypes() {
return this.columnTypes;
}
public void setColumnTypes(List<String> columnTypes) {
this.columnTypes = columnTypes;
}
#JsonProperty("Values")
public List<Object[]> getValues() {
return this.values;
}
public void setValues(List<Object[]> values) {
this.values = values;
}
}
// Top level Json POJO
public static class ResultContainer {
private Map<String, Result> results;
#JsonProperty("Results")
public Map<String, Result> getResults() {
return this.results;
}
public void setResults(Map<String, Result> results) {
this.results = results;
}
}
// A contract to map the result "values" to the expected object
public static interface ResultMapper<T> {
T map(TableResult map, Object[] row);
}
// Basic implementation for mapping user object from json "values[i]" array
public static class UserTableResultMapper implements ResultMapper<User> {
#Override
public User map(TableResult result, Object[] row) {
User user = new User();
// Here use any mapper logic based on column name
// Retrieved from result object.
// Below are for illustration only
user.setId(String.valueOf(row[0]));
user.setDocumentId(String.valueOf(row[1]));
return user;
}
}
// A result reader class
public static class ResultReader<T> implements Iterable<T> {
private TableResult result;
private ResultMapper<T> mapper;
public ResultReader(TableResult result, ResultMapper<T> mapper) {
this.result = result;
this.mapper = mapper;
}
#Override
public Iterator<T> iterator() {
final Iterator<Object[]> itr = result.getValues().iterator();
return new Iterator<T>() {
#Override
public void remove() {
throw new UnsupportedOperationException();
}
#Override
public T next() {
Object[] values = itr.next();
return mapper.map(result, values);
}
#Override
public boolean hasNext() {
return itr.hasNext();
}
};
};
}
public static class User {
private String id;
private String documentId;
// and others
public String getId() {
return this.id;
}
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
public void setId(String id) {
this.id = id;
}
public String getDocumentId() {
return this.documentId;
}
}
#Test
public void simpleTest() throws Exception {
ObjectMapper mapper = new ObjectMapper();
ResultContainer file = mapper.readValue(inputJson, ResultContainer.class);
Result result = file.getResults().get("output1");
ResultReader<User> userResultReader = new ResultReader<>(result.getValue(), new UserTableResultMapper());
for (User user : userResultReader) {
System.out.println(user.getId() + " : " + user.getDocumentId());
}
}
}
If you know exactly the structure of your json (like the json you have post) then you can using Gson to get your object like this:
JsonParser parser = new JsonParser();
JsonObject json = (JsonObject) parser.parse("your_json_string_here");
String column = json.get("Results").getAsJsonObject().get("output1").getAsJsonObject().get("value").getAsJsonObject().get("ColumnNames").getAsJsonArray().toString();
String value = json.get("Results").getAsJsonObject().get("output1").getAsJsonObject().get("value").getAsJsonObject().get("Values").getAsJsonArray().toString();
System.out.println(column);
System.out.println(value);
If you need some things more generic then you can parse your json string to a HashMap<String, Object> then using recursion to read the HashMap and get the value you want.
Example (in my code, the type of Map will corresponding to a Json Object, type of List will corresponding to the Array in Json string):
Type type = new TypeToken<HashMap<String, Object>>() {}.getType();
Gson gson = new Gson();
HashMap<String, Object> map = gson.fromJson("your_json_string_here", type);
for (String key : map.keySet()) {
Object obj = map.get(key);
if (obj instanceof List) {
for (Object o : (List) obj) {
if (o instanceof Map) {
loop((Map) o);
} else {
System.out.println(key + " : " + o);
}
}
} else if (obj instanceof Map) {
loop((Map) obj);
} else {
System.out.println(key + " : " + obj);
}
}
}
private static void loop(Map<String, Object> map) {
for (String key : map.keySet()) {
Object obj = map.get(key);
if (obj instanceof List) {
for (Object o : (List) obj) {
if (o instanceof Map) {
loop((Map) o);
} else {
System.out.println(key + " : " + o);
}
}
} else if (obj instanceof Map) {
loop((Map) obj);
} else {
System.out.println(key + " : " + obj);
}
}
}
Neither Jackson nor any other library will parse the Values array into objects with client data like your POJO. You can achieve this by getting the raw tree of data in this JSON and constructing objects by iterating over the Values array inside this tree. Assuming the order of ColumnNames is fixed then you can parse with Jackson like this:
final ObjectMapper mapper = new ObjectMapper();
final JsonNode tree = mapper.readTree(json);
final JsonNode values = tree.findValue("Values");
final List<ClientPOJO> clients = new ArrayList<>();
for (JsonNode node : values) {
final ClientPOJO client = new ClientPOJO();
client.setUserId(node.get(0).asText());
client.setDocumentId(node.get(1).asText());
client.setScoredLabels(node.get(2).asBoolean());
client.setScoredProbabilities(node.get(3).asDouble());
clients.add(client);
}
Docs for JsonNode. Basically with findValue you can get another node deep into the tree, with get you can get array elements by index and with asText etc you parse a value in JSON into the appropriate type in Java.
Since you seem to be flexible in choice of JSON parsing library I would suggest Jackson 2 from com.fasterxml instead of Jackson 1 from org.codehaus that you tried.

Jackson Json deserialization of an object to a list

I'm consuming a web service using Spring's RestTemplate and deserializing with Jackson.
In my JSON response from the server, one of the fields can be either an object or a list. meaning it can be either "result": [{}] or "result": {}.
Is there a way to handle this kind of things by annotations on the type I'm deserializing to ? define the member as an array[] or List<> and insert a single object in case of the second example ?
Can I write a new HttpMessageConverter that will handle it ?
Since you are using Jackson I think what you need is JsonDeserializer class (javadoc).
You can implement it like this:
public class ListOrObjectGenericJsonDeserializer<T> extends JsonDeserializer<List<T>> {
private final Class<T> cls;
public ListOrObjectGenericJsonDeserializer() {
final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
this.cls = (Class<T>) type.getActualTypeArguments()[0];
}
#Override
public List<T> deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
final ObjectCodec objectCodec = p.getCodec();
final JsonNode listOrObjectNode = objectCodec.readTree(p);
final List<T> result = new ArrayList<T>();
if (listOrObjectNode.isArray()) {
for (JsonNode node : listOrObjectNode) {
result.add(objectCodec.treeToValue(node, cls));
}
} else {
result.add(objectCodec.treeToValue(listOrObjectNode, cls));
}
return result;
}
}
...
public class ListOrObjectResultItemJsonDeserializer extends ListOrObjectGenericJsonDeserializer<ResultItem> {}
Next you need to annotate your POJO field. Let's say you have classes like Result and ResultItem:
public class Result {
// here you add your custom deserializer so jackson will be able to use it
#JsonDeserialize(using = ListOrObjectResultItemJsonDeserializer.class)
private List<ResultItem> result;
public void setResult(final List<ResultItem> result) {
this.result = result;
}
public List<ResultItem> getResult() {
return result;
}
}
...
public class ResultItem {
private String value;
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
}
Now you can check your deserializer:
// list of values
final String json1 = "{\"result\": [{\"value\": \"test\"}]}";
final Result result1 = new ObjectMapper().readValue(json1, Result.class);
// one value
final String json2 = "{\"result\": {\"value\": \"test\"}}";
final Result result2 = new ObjectMapper().readValue(json2, Result.class);
result1 and result2 contain the same value.
You can achieve what you want with a configuration flag in Jackson's ObjectMapper:
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.build();
Just set this ObjectMapper instance to your RestTemplate as explained in this answer, and in the class you are deserializing to, always use a collection, i.e. a List:
public class Response {
private List<Result> result;
// getter and setter
}

How to map an Hashmap to key-value-attributes in XML using xstream

I have the following entity:
#XStreamAlias("entity")
public class MapTestEntity {
#XStreamAsAttribute
public Map<String, String> myMap = new HashMap<>();
#XStreamAsAttribute
public String myText;
}
I use it with xstream like:
MapTestEntity e = new MapTestEntity();
e.myText = "Foo";
e.myMap.put("firstname", "homer");
e.myMap.put("lastname", "simpson");
XStream xstream = new XStream(new PureJavaReflectionProvider());
xstream.processAnnotations(MapTestEntity.class);
System.out.println(xstream.toXML(e));
and get the following xml:
<entity myText="Foo">
<myMap>
<entry>
<string>lastname</string>
<string>simpson</string>
</entry>
<entry>
<string>firstname</string>
<string>homer</string>
</entry>
</myMap>
</entity>
But I need to map the HashMap to attributes in xml like:
<entity myText="Foo" lastname="simpson" firstname="homer" />
How can I do that with XStream? Can I use a custom converter or mapper or something like that? TIA!!
(Of course my code needs to be ensure that are no duplicates in xml attributes.)
I wrote an own Converter:
public class MapToAttributesConverter implements Converter {
public MapToAttributesConverter() {
}
#Override
public boolean canConvert(Class type) {
return Map.class.isAssignableFrom(type);
}
#Override
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
Map<String, String> map = (Map<String, String>) source;
for (Map.Entry<String, String> entry : map.entrySet()) {
writer.addAttribute(entry.getKey(), entry.getValue().toString());
}
}
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, String> map = new HashMap<String, String>();
for (int i = 0; i < reader.getAttributeCount(); i++) {
String key = reader.getAttributeName(i);
String value = reader.getAttribute(key);
map.put(key, value);
}
return map;
}
}
The NamedMapConverter can achieve this. Take a look at http://x-stream.github.io/javadoc/com/thoughtworks/xstream/converters/extended/NamedMapConverter.html
The third example shows exactly that, what you want:
new NamedMapConverter(xstream.getMapper(), "entry", "key", String.class, "value", Integer.class, true, true, xstream.getConverterLookup());
Creates this xml output:
<map>
<entry key="keyValue" value="0"/>
</map>

How to marshal Map into {key: value, key: value, ...} with MOXy

using Eclipselink MOXy, I have the following class:
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
#XmlType(name = "")
public class MyObject {
private Map<String, String> meta;
#XmlPath(".")
#XmlJavaTypeAdapter(MetaMapAdapter.class)
public Map<String, String> getMeta() {
return meta;
}
public setMeta(Map<String, String> m) {
meta = m;
}
}
My AdaptedMap looks like this (credits to JAXB: how to marshall map into <key>value</key>):
import javax.xml.bind.annotation.XmlAnyElement;
public class AdaptedMap {
private Object value;
public AdaptedMap() {}
#XmlAnyElement
public Object getValue() { return value; }
public void setValue(final Object value) { this.value = value; }
}
And the MapAdapter looks like this (credits to JAXB: how to marshall map into <key>value</key>):
import java.util.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.eclipse.persistence.oxm.XMLRoot;
import org.w3c.dom.*;
public class MetaMapAdapter extends XmlAdapter<AdaptedMap, Map<String, String>> {
public MapAdapter() {}
#Override public AdaptedMap marshal(final Map<String, String> map) throws Exception {
if (map == null) { return null; }
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
final Document document = db.newDocument();
final Element rootElement = document.createElement(getTagName());
document.appendChild(rootElement);
for (final Entry<String, String> entry : map.entrySet()) {
final Element mapElement = document.createElement(entry.getKey());
mapElement.setTextContent(entry.getValue());
rootElement.appendChild(mapElement);
}
final AdaptedMap adaptedMap = new AdaptedMap();
adaptedMap.setValue(document);
return adaptedMap;
}
#Override public Map<String, String> unmarshal(final AdaptedMap adaptedMap) {
if (adaptedMap == null) { return null; }
final Map<String, String> map = new HashMap<String, String>();
final Element rootElement = (Element) adaptedMap.getValue();
final NodeList childNodes = rootElement.getChildNodes();
for (int x = 0, size = childNodes.getLength(); x < size; x++) {
final Node childNode = childNodes.item(x);
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
map.put(childNode.getLocalName(), childNode.getTextContent());
}
}
return map;
}
}
By using Eclipselink MOXy, I'm able to get this JSON in return with the help of XmlPath:
{
"meta": {
"akey":"avalue",
"bkey":"bvalue"
}
}
Unfortunately, I'm unable to unmarshal to MyObject in reverse due to the usage of XmlPath to collapse the outer meta element.
On a side note, I'm also not able to use the new XmlVariableNode in Eclipselink 2.6 as I'm only allowed to use stable releases of the API :(
Anyone knows how I can resolve this?
On a side note, I'm also not able to use the new XmlVariableNode in
Eclipselink 2.6 as I'm only allowed to use stable releases of the API
:(
#XmlVariableNode has also been included in EclipseLink 2.5.1 which is now released:
http://www.eclipse.org/eclipselink/downloads/
This annotation is well suited for mapping your use case:
http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-using-maps-key-as.html

Categories

Resources