I am building a rest API using Jersey where XML and JSON outputs are allowed depending on what format client prefers(using Accept header).The service sends the below class as an output which looks like this
#XmlRootElement
public class ProjectDetails{
private List<Attachment> attachments;
private Map<String, List<Attachment>> imageCategory;
#XmlTransient
public List<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
public Map<String, List<Attachment>> getImageCategory() {
if(attachments == null || attachments.size() == 0){
return null;
}
Map<String, List<Attachment>> map = new HashMap<String, List<Attachment>>();
for (Attachment img : attachments){
String key = img.getCategory();
if(BaseUtil.hasText(key)){
List<Attachment> values = map.get(key);
if (values == null){
values = new ArrayList<Attachment>();
}
values.add(img);
map.put(key, values);
}
}
this.imageCategory = map ;
return imageCategory;
}
public void setImageCategory(Map<String, List<Attachment>> imageCategory) {
this.imageCategory = imageCategory;
}
}
I don't want attachments field as an output so marked it with #XmlTransient rather I want to form a Map using the attachments field and send it as an output.
In case of JSON format, I am getting the correct response.But in case of XML, I am not getting any output when I hit the service.
I think it is related to this Map field because if I remove Map field and add some other field like String then I get that field in response.
Please let me know how to resolve this.
Update:
After some googling, i found XmlAdapter solution and implemented as below
public class MapAdapter extends
XmlAdapter<MapAdapter.AdaptedMap, Map<String, List<Attachment>>> {
public static class AdaptedEntry {
public String key;
public List<Attachment> value = new ArrayList<Attachment>();
}
public static class AdaptedMap {
List<AdaptedEntry> entries = new ArrayList<AdaptedEntry>();
}
#Override
public AdaptedMap marshal(Map<String, List<Attachment>> map)
throws Exception {
AdaptedMap adaptedMap = new AdaptedMap();
for (Entry<String, List<Attachment>> entry : map.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
#Override
public Map<String, List<Attachment>> unmarshal(AdaptedMap adaptedMap)
throws Exception {
List<AdaptedEntry> adapatedEntries = adaptedMap.entries;
Map<String, List<Attachment>> map = new HashMap<String, List<Attachment>>(
adapatedEntries.size());
for (AdaptedEntry adaptedEntry : adapatedEntries) {
map.put(adaptedEntry.key, adaptedEntry.value);
}
return map;
}
And then
#XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, String> getImageCategory() {
But still it's not working..Anything I missed?
I have used your ProjectDetails class a little bit changes I've made, and it provides response for both XML and JSON. Can you try this?
#XmlRootElement
public class ProjectDetails {
private List<Attachment> attachments;
private Map<String, ArrayList<Attachment>> imageCategory;
#XmlTransient
public List<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
#XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, ArrayList<Attachment>> getImageCategory() {
if(attachments == null || attachments.size() == 0){
return null;
}
Map<String, ArrayList<Attachment>> map = new HashMap<String, ArrayList<Attachment>>();
for (Attachment img : attachments){
String key = img.getCategory();
if(!key.equals("")){
ArrayList<Attachment> values = map.get(key);
if (values == null){
values = new ArrayList<Attachment>();
}
values.add(img);
map.put(key, values);
}
}
this.imageCategory = map ;
return imageCategory;
}
public void setImageCategory(Map<String, ArrayList<Attachment>> imageCategory) {
this.imageCategory = imageCategory;
}
}
And the Adapter class you can use the following
public class MapAdapter extends XmlAdapter<MapElement[], Map<String, ArrayList<Attachment>>>{
public MapElement[] marshal(Map<String, ArrayList<Attachment>> arg0) throws Exception {
MapElement[] mapElements = new MapElement[arg0.size()];
int i = 0;
for (Map.Entry<String, ArrayList<Attachment>> entry : arg0.entrySet()){
mapElements[i++] = new MapElement(entry.getKey(), entry.getValue());
}
return mapElements;
}
public Map<String, ArrayList<Attachment>> unmarshal(MapElement[] arg0) throws Exception {
Map<String, ArrayList<Attachment>> r = new HashMap<String, ArrayList<Attachment>>();
for (MapElement mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}
I've changed the MapElement also
public class MapElement {
#XmlElement
public String key;
#XmlElement
public ArrayList<Attachment> value;
private MapElement() {
}
public MapElement(String key, ArrayList<Attachment> value) {
this.key = key;
this.value = value;
}
}
And the Attachement class should have getter setter methods
public class Attachment {
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
private String category;
public Attachment(String cat){
this.category = cat;
}
}
Related
Now I have a simple enum called AppName:
package misc.enumn.app;
import lombok.Getter;
import misc.enumn.BaseEnum;
/**
* #author dolphin
*/
#Getter
public enum AppName implements BaseEnum {
CRUISE( 1, "cruise"),
BACK(2, "back"),
;
private Integer key;
private String value;
AppName(Integer key, String value) {
this.key = key;
this.value = value;
}
public void setKey(Integer key) {
this.key = key;
}
public void setValue(String value) {
this.value = value;
}
public static AppName getAppMarkByValue(String value) {
AppName datetimeType = null;
for (AppName type : AppName.values()) {
if (type.name().equals(value)) {
datetimeType = type;
}
}
return datetimeType;
}
public static AppName getAppMarkByKey(Short key) {
AppName datetimeType = null;
for (AppName type : AppName.values()) {
if (type.key.equals(key)) {
datetimeType = type;
}
}
return datetimeType;
}
}
then I define a request object like this:
#Data
#NoArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class UserLoginRequest implements Serializable {
#ApiModelProperty(value = "app")
private AppName app;
}
when I passed appId 1 to the server side, the server parsed AppName as BACK, I do not understand why it parsed as the BACK not 'CRUISE'? I have already define the enum parser:
public class IntegerCodeToEnumConverterFactory implements ConverterFactory<Integer, BaseEnum> {
private static final Map<Class, Converter> CONVERTERS = Maps.newHashMap();
#Override
public <T extends BaseEnum> Converter<Integer, T> getConverter(Class<T> targetType) {
Converter<Integer, T> converter = CONVERTERS.get(targetType);
if (converter == null) {
converter = new IntegerToEnumConverter<>(targetType);
CONVERTERS.put(targetType, converter);
}
return converter;
}
}
and add to interceptor config:
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new IntegerCodeToEnumConverterFactory());
}
but seem still could not parse the enum, what should I do to make it parse the app correctly? this is the wrong parse(I want 1 parsed as CRUISE and 2 parsed as BACK):
By the way, when I replace the app from enum as Integer, it could parse it correctly(receive value 1). But I think using enum may be better for readable.
public class IntegerToEnumConverter <T extends BaseEnum> implements Converter<Integer, T> {
private Map<Integer, T> enumMap = Maps.newHashMap();
public IntegerToEnumConverter(Class<T> enumType) {
T[] enums = enumType.getEnumConstants();
for (T e : enums) {
enumMap.put(e.getKey(), e);
}
}
#Override
public T convert(Integer source) {
T t = enumMap.get(source);
if (ObjectUtils.isNull(t)) {
throw new IllegalArgumentException("");
}
return t;
}
}
what I am doing only support http get method, if you want to parse enum in http post method. define the code like this:
#JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static AppName resolve(Integer key) {
return mappings.get(key);
}
this is my full code:
#Getter
public enum AppName implements BaseEnum {
CRUISE(1, "cruise"),
BACK(2, "back"),
;
#JsonValue
private Integer key;
private String value;
AppName(Integer key, String value) {
this.key = key;
this.value = value;
}
private static final Map<Integer, AppName> mappings;
static {
Map<Integer, AppName> temp = new HashMap<>();
for (AppName courseType : values()) {
temp.put(courseType.key, courseType);
}
mappings = Collections.unmodifiableMap(temp);
}
#EnumConvertMethod
#JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static AppName resolve(Integer key) {
return mappings.get(key);
}
public void setKey(Integer key) {
this.key = key;
}
public void setValue(String value) {
this.value = value;
}
public static AppName getAppMarkByValue(String value) {
AppName datetimeType = null;
for (AppName type : AppName.values()) {
if (type.name().equals(value)) {
datetimeType = type;
}
}
return datetimeType;
}
public static AppName getAppMarkByKey(Short key) {
AppName datetimeType = null;
for (AppName type : AppName.values()) {
if (type.key.equals(key)) {
datetimeType = type;
}
}
return datetimeType;
}
}
I have the following POJO that can be serialized into bytes or json.
public final class Message {
private final Data data;
private final Request request;
private final Response response;
public Message() {
this.data = new Data();
this.request = new Request();
this.response = new Response();
}
public Data getData() {
return data;
}
public Request getRequest() {
return request;
}
public Response getResponse() {
return response;
}
public Object query(String pointer) {
return toJson().query(pointer);
}
public byte[] toBytes() {
try {
return new ObjectMapper(new MessagePackFactory()).writeValueAsBytes(this);
} catch (JsonProcessingException ex) {
throw new MessageException(ex);
}
}
public JSONObject toJson() {
try {
return new JSONObject(new ObjectMapper().writeValueAsString(this));
} catch (JsonProcessingException ex) {
throw new MessageException(ex);
}
}
#Override
public String toString() {
try {
return toString(0);
} catch (MessageException ex) {
throw new MessageException(ex);
}
}
public String toString(int indent) {
try {
return toJson().toString(indent);
} catch (MessageException ex) {
throw new MessageException(ex);
}
}
}
Reference Classes:
public class Data {
private final Map<String, Map<String, Object>> dataMap;
public Data() {
this.dataMap = new HashMap();
}
public Data addToSet(String name, String key, Object value) {
Map<String, Object> map = dataMap.get(name);
if (map == null) {
map = new HashMap();
}
map.put(key, value);
dataMap.put(name, map);
return this;
}
public Map<String, Map<String, Object>> getSets() {
return dataMap;
}
public Data updateSet(String name, String key, Object value) {
return Data.this.addToSet(name, key, value);
}
public Data removeFromSet(String name, String key) {
Map<String, Object> map = dataMap.get(name);
if (map == null) {
throw new MessageException("No such property '" + key + "' for set '" + name + "'");
}
map.remove(key);
return this;
}
public Map<String, Object> getSet(String name) {
return dataMap.get(name);
}
}
public class Request {
private String method;
private String resource;
private final Map<String, Object> body;
private final Map<String, String> headers;
private final Map<String, String[]> parameters;
public Request() {
this.body = new HashMap();
this.headers = new HashMap();
this.parameters = new HashMap();
}
public String getMethod() {
return Objects.toString(method, "");
}
public String getResource() {
return Objects.toString(resource, "");
}
public Map<String, Object> getBody() {
return body;
}
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, String[]> getParameters() {
return parameters;
}
public String getHeader(String name) {
return headers.get(name);
}
public Request setBody(String payload) {
try {
this.body.putAll(new ObjectMapper().readValue(payload, new TypeReference<Map<String, Object>>() {
}));
return this;
} catch (JsonProcessingException ex) {
throw new MessageException(ex);
}
}
public Request setMethod(String name) {
this.method = name;
return this;
}
public Request setResource(String name) {
this.resource = name;
return this;
}
public Request setHeaders(Map<String, String> headers) {
this.headers.putAll(headers);
return this;
}
public Request setParameters(Map<String, String[]> parameters) {
this.parameters.putAll(parameters);
return this;
}
}
public class Response {
private String code;
private String data;
private String messageId;
private String timestamp;
private String description;
public Response() {
}
public String getCode() {
return Objects.toString(code, "");
}
public String getData() {
return Objects.toString(data, "");
}
public String getMessageId() {
return Objects.toString(messageId, "");
}
public String getTimestamp() {
return Objects.toString(timestamp, "");
}
public String getDescription() {
return Objects.toString(description, "");
}
public Response setCode(String code) {
this.code = code;
return this;
}
public Response setData(String data) {
this.data = data;
return this;
}
public Response setMessageId(String messageId) {
this.messageId = messageId;
return this;
}
public Response setTimestamp(String timestamp) {
this.timestamp = timestamp;
return this;
}
public Response setDescription(String description) {
this.description = description;
return this;
}
}
When serializing to json I get a valid string
{
"request": {
"headers": {},
"method": "",
"resource": "",
"body": {
"whatsapp": {
"conversationId": "39f09c41-1bd3-4e81-b829-babed3747d4b",
"name": "Dave",
"source": "+123456789098"
},
"payment": {
"product": "chocolate",
"amount": 1,
"method": "cashapp",
"msisdn": "123456789098",
"entity": "The Fudge Shop"
}
},
"parameters": {}
},
"data": {
"sets": {
"whatsapp": {
"provider": "clickatell",
"name": "Dave",
"destination": "123456789098",
"source": "123456789098",
"message": "Your payment of $1.00 received, your receipt.no is QWJ124XPA9."
},
"cashapp": {
"amount": 1,
"receiptNo": "QWJ124XPA9",
"name": "Dave Chapelle",
"msisdn": "123456789098"
}
}
},
"response": {
"code": "202",
"data": "",
"messageId": "20210623160202a647d32ee9ae477f9c90d8b1fbfd763a",
"description": "Processing Request",
"timestamp": "2021-06-23 16:02:02.408"
}
}
When I attempt to deserialize the json back to a pojo
Message output = new ObjectMapper().readValue(json.toString(), Message.class);
I get the error :
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
The error seems to be generated from the Request class when attempting to deserialize the Map<String, Object> body:
How may I deserialize the Map correctly?
For the String-Problem, these sources might help:
Can not deserialize instance of java.lang.String out of START_OBJECT token
https://www.baeldung.com/jackson-map#1-mapltstring-stringgt-deserialization
Why this code can't work
Jackson is not much more powerful than you are.
If Jackson gets an object to serialize, it tries to serialize all of its values. And only its values (which is pretty good for the independence from classes). This is a json object:
{
"type":"apple",
"quantity":3,
"imageID":17
}
Now, what is the class of this object? It could be Fruit.class, Image.class or even RoundObject.class, json doesn't know and Jackson neither.
So how does json find out what the class is? By looking at the type of the object reference. In your case, it's Object. In Object.class, Jackson cannot find a constructor that requires the variables of the object that has been saved, so it crashes.
Solution
Trying to serialize objects is not a good idea. If you have very different classes you want to put in, e.g. Apple and Banana, make an interface or abstract class called Fruit that both of them implement. Now, use this annotation at the top of this class:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type") // name of the variable to save the kind of object you put in. NO VARIABLES in all classes that extend from Fruit are allowed to have this name (or at least #JsonProperty).
#JsonSubTypes({
#JsonSubTypes.Type(value = Apple.class, name = "banana"),
#JsonSubTypes.Type(value = Banana.class, name = "apple"),
})
And using a Map<String, Fruit> should work.
The solution that worked for me was using custom deserialization, #JsonDeserialize annotation & JsonDeserializer interface, in order to achieve the desired results.
Below is the solution:
public class Request {
private String method;
private String resource;
#JsonDeserialize(using = BodyDeserializer.class)
private final Map<String, Object> body;
private final Map<String, String> headers;
private final Map<String, String[]> parameters;
public Request() {
this.body = new HashMap();
this.headers = new HashMap();
this.parameters = new HashMap();
}
public String getMethod() {
return method;
}
public String getResource() {
return resource;
}
public Map<String, Object> getBody() {
return body;
}
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, String[]> getParameters() {
return parameters;
}
public String getHeader(String name) {
return headers.get(name);
}
public Request setBody(Map<String, Object> body) {
this.body.putAll(body);
return this;
}
public Request setMethod(String name) {
this.method = name;
return this;
}
public Request setResource(String name) {
this.resource = name;
return this;
}
public Request setHeaders(Map<String, String> headers) {
this.headers.putAll(headers);
return this;
}
public Request setParameters(Map<String, String[]> parameters) {
this.parameters.putAll(parameters);
return this;
}
private static class BodyDeserializer extends JsonDeserializer<Map<String, Object>> {
#Override
public Map<String, Object> deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonDeserializer<Object> deserializer = dc.findRootValueDeserializer(dc.constructType(Map.class));
Map<String, Object> map = (Map<String, Object>) deserializer.deserialize(jp, dc);
return map;
}
}
}
Try this one JacksonUtils
Message actual = createMessage();
String json = JsonUtils.prettyPrint().writeValue(actual);
System.out.println(json);
Message expected = JsonUtils.readValue(json, Message.class);
This is complete snippet:
public class MavenMain {
public static void main(String... args) {
Message actual = createMessage();
String json = JsonUtils.prettyPrint().writeValue(actual);
System.out.println(json);
Message expected = JsonUtils.readValue(json, Message.class);
}
private static Message createMessage() {
Message message = new Message();
message.setData(createData());
message.setRequest(createRequest());
message.setResponse(createResponse());
return message;
}
private static Data createData() {
Map<String, Object> whatsapp = new LinkedHashMap<>();
whatsapp.put("provider", "clickatell");
whatsapp.put("name", "Dave");
whatsapp.put("destination", "123456789098");
whatsapp.put("source", "123456789098");
whatsapp.put("message", "Your payment of $1.00 received, your receipt.no is QWJ124XPA9.");
Map<String, Object> cashapp = new LinkedHashMap<>();
cashapp.put("receiptNo", "QWJ124XPA9");
cashapp.put("name", "Dave Chapelle");
cashapp.put("msisdn", "123456789098");
Map<String, Map<String, Object>> dataMap = new LinkedHashMap<>();
dataMap.put("whatsapp", whatsapp);
dataMap.put("cashapp", cashapp);
Data data = new Data();
data.setDataMap(dataMap);
return data;
}
private static Request createRequest() {
Map<String, Object> whatsapp = new LinkedHashMap<>();
whatsapp.put("conversationId", "39f09c41-1bd3-4e81-b829-babed3747d4b");
whatsapp.put("name", "Dave");
whatsapp.put("source", "+123456789098");
Map<String, Object> payment = new LinkedHashMap<>();
payment.put("product", "chocolate");
payment.put("amount", 1);
payment.put("method", "cashapp");
payment.put("msisdn", "123456789098");
payment.put("entity", "The Fudge Shop");
Map<String, Object> body = new HashMap<>();
body.put("whatsapp", whatsapp);
body.put("payment", payment);
Request request = new Request();
request.setHeaders(Collections.emptyMap());
request.setMethod("");
request.setResource("");
request.setBody(body);
request.setParameters(Collections.emptyMap());
return request;
}
private static Response createResponse() {
Response response = new Response();
response.setCode("202");
response.setData("");
response.setMessageId("20210623160202a647d32ee9ae477f9c90d8b1fbfd763a");
response.setDescription("Processing Request");
response.setTimestamp("2021-06-23T16:02:02.408");
return response;
}
}
class Message {
private Data data;
private Request request;
private Response response;
public void setData(Data data) {
this.data = data;
}
public void setRequest(Request request) {
this.request = request;
}
public void setResponse(Response response) {
this.response = response;
}
}
class Data {
#JsonProperty("sets")
private Map<String, Map<String, Object>> dataMap;
public void setDataMap(Map<String, Map<String, Object>> dataMap) {
this.dataMap = dataMap;
}
}
class Request {
private String method;
private String resource;
private Map<String, Object> body;
private Map<String, String> headers;
private Map<String, String[]> parameters;
public void setMethod(String method) {
this.method = method;
}
public void setResource(String resource) {
this.resource = resource;
}
public void setBody(Map<String, Object> body) {
this.body = body;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public void setParameters(Map<String, String[]> parameters) {
this.parameters = parameters;
}
}
class Response {
private String code;
private String data;
private String messageId;
private String timestamp;
private String description;
public void setCode(String code) {
this.code = code;
}
public void setData(String data) {
this.data = data;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public void setDescription(String description) {
this.description = description;
}
}
In case you want to use immutable object, then it's a bit another configuration of models, but code in the main class will be the same.
I'm using Android Studio and I want to make a listview, which contains values that are received by JSON.
protected Void doInBackground(Void... voids) {
HttpHandler Handler = new HttpHandler();
String JSONString = Handler.makeServiceCall(JSONUrl);
Log.e(TAG, "Response:" + JSONString);
if(JSONString != null){
try {
JSONObject CountriesJSONObject = new JSONObject(JSONString);
JSONArray Countries = CountriesJSONObject.getJSONArray("countries");
for (int i = 1; i < Countries.length(); i++) {
JSONObject Country = Countries.getJSONObject(i);
//Details
String CountryID = Country.getString("id");
String CountryName = Country.getString("name");
String CountryImage = Country.getString("image");
//Hashmap
HashMap<String, String> TempCountry = new HashMap<>();
//Details to Hashmap
TempCountry.put("id", CountryID);
TempCountry.put("name", CountryName);
TempCountry.put("image", CountryImage);
//Hashmap to Countrylist
CountryList.add(TempCountry);
}
} catch (final JSONException e){
Log.e(TAG,e.getMessage());
ProgressDialog.setMessage("Error loading Data!");
}
}
return null;
}
This is the code for getting the JSON values, and i'm receiving an error
"No value for id"
What am I doing wrong?
You still have the "country" key to unwrap. Try like this:
for (int i = 1; i < Countries.length(); i++) {
JSONObject Country = Countries.getJSONObject(i).getJSONObject("country");
//Details
String CountryID = Country.getString("id");
String CountryName = Country.getString("name");
String CountryImage = Country.getString("image");
//Hashmap
HashMap<String, String> TempCountry = new HashMap<>();
//Details to Hashmap
TempCountry.put("id", CountryID);
TempCountry.put("name", CountryName);
TempCountry.put("image", CountryImage);
//Hashmap to Countrylist
CountryList.add(TempCountry);
}
First step is to create a new Java class model for the JSON - you can just copy and paste this.
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
public class Countries {
public class CountriesList implements Serializable {
private Country[] countries;
public Country[] getCountries() {
return countries;
}
public void setCountries(Country[] countries) {
this.countries = countries;
}
public ArrayList<Country> getCountriesAsList() {
if(countries == null || countries.length == 0) {
return new ArrayList<>();
} else {
return (ArrayList<Country>) Arrays.asList(countries);
}
}
}
public class Country implements Serializable {
private String id;
private String name;
private String image;
public Country() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
}
}
Now, it's simply converting the JSON into Java object like this. You can use that ArrayList for adapter or however you like.
protected Void doInBackground(Void... voids) {
HttpHandler Handler = new HttpHandler();
String jsonString = Handler.makeServiceCall(JSONUrl);
Countries.CountriesList countries = new Gson().fromJson(jsonString, Countries.CountriesList.class);
// this is the full list of all your countries form json
ArrayList<Countries.Country> countryList = countries.getCountriesAsList();
}
Note: you will need the Gson library to use the solution I showed above. I use that to convert JSON into Java object.
compile 'com.google.code.gson:gson:2.8.0'
I have the following code:
#XmlRootElement(name = "repository")
#XmlAccessorType(XmlAccessType.FIELD)
public class Repository
{
#XmlElement (name = "id")
private String id;
#XmlElement (name = "policy")
private String policy;
...
}
#XmlRootElement(name = "storage")
#XmlAccessorType(XmlAccessType.FIELD)
public class Storage
{
#XmlElement (name = "id")
private String id;
/**
* K: repository.id
* V: Repository
*/
#XmlElement (name = "repositories")
#XmlJavaTypeAdapter(RepositoryMapAdapter.class)
private Map<String, Repository> repositories = new LinkedHashMap<String, Repository>();
...
}
public class DataCenter
{
/**
* K: storageId
* V: storage
*/
#XmlElement(name = "storages")
#XmlJavaTypeAdapter(StorageMapAdapter.class)
//#XStreamAlias(value = "storages")
private Map<String, Storage> storages = new LinkedHashMap<String, Storage>();
...
}
I have the following two adapters:
public class RepositoryMapAdapter
extends XmlAdapter<RepositoryMapAdapter.RepositoryMap, Map<String, Repository>>
{
public static class RepositoryMap
{
#XmlVariableNode("id")
List<RepositoryMapEntry> entries = new ArrayList<RepositoryMapEntry>();
}
public static class RepositoryMapEntry
{
#XmlAttribute
public String id;
#XmlValue
public Repository repository;
}
#Override
public RepositoryMap marshal(Map<String, Repository> map)
throws Exception
{
RepositoryMap repositoryMap = new RepositoryMap();
for (Map.Entry<String, Repository> entry : map.entrySet())
{
RepositoryMapEntry repositoryMapEntry = new RepositoryMapEntry();
repositoryMapEntry.id = entry.getKey();
repositoryMapEntry.repository = entry.getValue();
System.out.println("Writing repository " + entry.getValue().getId());
repositoryMap.entries.add(repositoryMapEntry);
}
return repositoryMap;
}
#Override
public Map<String, Repository> unmarshal(RepositoryMap repositoryMap)
throws Exception
{
List<RepositoryMapEntry> adaptedEntries = repositoryMap.entries;
Map<String, Repository> map = new LinkedHashMap<String, Repository>(adaptedEntries.size());
for (RepositoryMapEntry repositoryMapEntry : adaptedEntries)
{
System.out.println("Reading repository " + repositoryMapEntry.id);
map.put(repositoryMapEntry.id, repositoryMapEntry.repository);
}
return map;
}
}
public class StorageMapAdapter
extends XmlAdapter<StorageMapAdapter.StorageMap, Map<String, Storage>>
{
public static class StorageMap
{
#XmlVariableNode("id")
List<StorageMapEntry> entries = new ArrayList<StorageMapEntry>();
}
public static class StorageMapEntry
{
#XmlAttribute
public String id;
#XmlValue
public Storage storage;
}
#Override
public StorageMap marshal(Map<String, Storage> map)
throws Exception
{
StorageMap storageMap = new StorageMap();
for (Map.Entry<String, Storage> entry : map.entrySet())
{
StorageMapEntry storageMapEntry = new StorageMapEntry();
storageMapEntry.id = entry.getKey();
storageMapEntry.storage = entry.getValue();
System.out.println("Writing storage " + entry.getValue().getId());
storageMap.entries.add(storageMapEntry);
}
return storageMap;
}
#Override
public Map<String, Storage> unmarshal(StorageMap storageMap)
throws Exception
{
List<StorageMapEntry> adaptedEntries = storageMap.entries;
Map<String, Storage> map = new LinkedHashMap<String, Storage>(adaptedEntries.size());
for (StorageMapEntry storageMapEntry : adaptedEntries)
{
System.out.println("Reading storage " + storageMapEntry.id);
map.put(storageMapEntry.id, storageMapEntry.storage);
}
return map;
}
}
I'd like to have the following XML:
<storages>
<storage id="storage0">
<repositories>
<repository id="repository1" policy="policy1"/>
<repository id="repository2" policy="policy2"/>
</repositories>
</storage>
<storage id="storage1">
<repositories>
<repository id="repository3" />
<repository id="repository4" />
</repositories>
</storage>
</storages>
Based on the above XML, what more do I need to do in order to have my code read/write such XML using JAXB? As far as I understand, I need to use XmlAdapter, but I'm not quite sure how to apply it to this case.
Something in the lines of:
#XmlRootElement(name = "datacenter")
public class DataCenter {
/**
* K: storageId V: storage
*/
#XmlElement(name = "storages")
#XmlJavaTypeAdapter(StorageMapAdapter.class)
private final Map<String, Storage> storages = new LinkedHashMap<String, Storage>();
}
#XmlJavaTypeAdapter(StorageMapAdapter.class)
#XmlAccessorType(XmlAccessType.FIELD)
public class Storage {
#XmlAttribute(name = "id")
private String id;
/**
* K: repository.id V: Repository
*/
#XmlElement(name = "repositories")
#XmlJavaTypeAdapter(RepositoryMapAdapter.class)
private final Map<String, Repository> repositories = new LinkedHashMap<String, Repository>();
}
public class StorageMap {
#XmlElement(name = "storage")
List<Storage> entries = new ArrayList<Storage>();
public List<Storage> getEntries() {
return entries;
}
}
public class StorageMapAdapter extends XmlAdapter<StorageMap, Map<String, Storage>> {
#Override
public StorageMap marshal(Map<String, Storage> map) throws Exception {
StorageMap storageMap = new StorageMap();
for (Map.Entry<String, Storage> entry : map.entrySet()) {
storageMap.getEntries().add(entry.getValue());
}
return storageMap;
}
#Override
public Map<String, Storage> unmarshal(StorageMap storageMap) throws Exception {
List<Storage> adaptedEntries = storageMap.entries;
Map<String, Storage> map = new LinkedHashMap<String, Storage>(adaptedEntries.size());
for (Storage storage : adaptedEntries) {
map.put(storage.getId(), storage);
}
return map;
}
}
#XmlRootElement(name = "repository")
#XmlAccessorType(XmlAccessType.FIELD)
public class Repository {
#XmlAttribute(name = "id")
private String id;
#XmlAttribute(name = "policy")
private String policy;
}
public class RepositoryMap {
#XmlElement(name = "repository")
List<Repository> entries = new ArrayList<Repository>();
public List<Repository> getEntries() {
return entries;
}
}
public class RepositoryMapAdapter extends XmlAdapter<RepositoryMap, Map<String, Repository>> {
#Override
public RepositoryMap marshal(Map<String, Repository> map) throws Exception {
RepositoryMap repositoryMap = new RepositoryMap();
for (Map.Entry<String, Repository> entry : map.entrySet()) {
repositoryMap.getEntries().add(entry.getValue());
}
return repositoryMap;
}
#Override
public Map<String, Repository> unmarshal(RepositoryMap repositoryMap) throws Exception {
List<Repository> adaptedEntries = repositoryMap.entries;
Map<String, Repository> map = new LinkedHashMap<String, Repository>(adaptedEntries.size());
for (Repository repository : adaptedEntries) {
System.out.println("Reading repository " + repository.getId());
map.put(repository.getId(), repository);
}
return map;
}
}
Running demo here: http://ideone.com/NyOGVQ
Demo updated with marshalling here: http://ideone.com/NzvRzX
I would like to be able to test if a List contain an object with a given key-value
For example, I would like to do something like Iterables.contains(l2, "lname", "Jordan")); instead of having to create all other Map objects like below in l2
//List<String> l = Arrays.asList("Mickael", "Jordan", "His Airness");
//System.out.println(Iterables.contains(l, "Jordan"));
Map<String, String> p1 = new HashMap<String, String>();
p1.put("fname", "Mickael");
p1.put("lname", "Jordan");
p1.put("nname", "His Airness");
Map<String, String> p2 = new HashMap<String, String>();
p2.put("fname", "Paul");
p2.put("lname", "Pierce");
p2.put("nname", "The Truth");
List<Map<String, String>> l2 = Arrays.asList(p1, p2);
Map<String, String> p3 = new HashMap<String, String>();
p3.put("fname", "Mickael"); //
p3.put("lname", "Jordan");
p3.put("nname", "His Airness"); //
System.out.println(Iterables.contains(l2, p3));
I'd like to know if there's such guava's function, and not doing a loop on l2 and testing each elt.get("lname")
Edit
3 solutions answered: trying to see which one is more perfomant
System.out.println(Iterables.any(l2, withEntry("lname", "Jordan"))); //#axtavt
System.out.println(has("lname", "Jordan")); //#JB
System.out.println(Iterables.any(l2, new KeyValuePredicate("lname", "Jordan"))); //#JB
public static Boolean has(final String key, final String value) {
return Iterables.any(l2, new Predicate<Map<String, String>>() {
#Override
public boolean apply(Map<String, String> input) {
return input.get(key).equals(value);
}
});
}
public static Predicate<Map<String, String>> withEntry(final String key, final String value) {
return new Predicate<Map<String, String>>() {
public boolean apply(Map<String, String> input) {
return value.equals(input.get(key));
}
};
}
class KeyValuePredicate implements Predicate<Map<String, String>>{
private String key;
private String value;
public KeyValuePredicate(String key, String value) {
super();
this.key = key;
this.value = value;
}
#Override
public boolean apply(Map<String, String> arg0) {
// TODO Auto-generated method stub
return arg0.get(key).equals(value);
}
}
return Iterables.any(l2, new Predicate<Map<String, String>>() {
#Override
public boolean apply(Map<String, String> input) {
return input.get("lname").equals("Jordan");
}
});
But you're using maps when you should use objects with properties.
Of course, if you need to do that multiple times, with various properties, you should transform the predicate into a non-anonymous, reusable class:
return Iterables.any(l2, new KeyValuePredicate("lname", "Jordan"));
You can implement an appropriate Predicate and use Iterables.any():
public Predicate<Map<String, String>> withEntry(final String key, final String value) {
return new Predicate<Map<String, String>>() {
public boolean apply(Map<String, String> input) {
return value.equals(input.get(key));
}
};
}
System.out.println(Iterables.any(l2, withEntry("lname", "Jordan")));
Well this is straightforward.
You should create a proper entity class:
public class Person {
private String fName;
private String lName;
private String nName;
public Person(String fName, String lName, String nName) {
this.fName = fName;
this.lName = lName;
this.nName = nName;
}
public String getFName() {
return fName;
}
public String getLName() {
return lName;
}
public String getNName() {
return nName;
}
}
Then you can do the following:
import java.util.*;
public class Test {
public static void main (String [] args) {
List<Person> list = new ArrayList<Person>();
Person p1 = new Person("Mickael", "Jordan", "His Airness");
for (Person person : list) {
if (person.getFName().equals("Mickael")) {
System.out.println("Mickael is in the list!");
break;
}
}
}
}