I am using Retrofit and SimpleXMLConverter to get and deserialize an XML file. I am converting an attribute to type long. It would work fine if the attribute wasn´t sometimes "", aka empty.
I tried using
#Attribute(empty="-1")
for the cases this attribute should be empty, so it should return "-1" but it does not do so. Is the usage of this empty attribute correct?
It would work fine if the attribute wasn´t sometimes "", aka empty.
This is not correct here: The attributes value - seen as string - is not empty in the meaning of missing or "nothing", it's an empty string.
Here's an example:
XML:
<root value=""></root>
Java class:
#Root(name = "root")
public class Example
{
#Attribute(name = "value", empty = "-1", required = false)
private long value;
#Override
public String toString()
{
return "Example{" + "value=" + value + '}';
}
}
This throws - and that's reasonable - a NumberFormatException. If you replace the type of value with String you wont catch an exception, value is set as an empty string (""). On the other hand, keeping string type but removing the attribute in XML will set "-1" as value (that's why required = false is used). Now the Serializer can't find any value and therefore sets the default one.
You could handle this in your class internally, like let the corresponding getter-method return -1 in case of an empty string:
public long getValue()
{
if( value == null || value.isEmpty() == true )
{
return -1;
}
return Long.valueOf(value);
}
(Don't forget to change your code according this - in my example you have to change toString()-method)
But there's a better solution: Simple allows you to implement custom Transformer for any types (don't mix with Converter!). With these you can implement type -> String (write) and String -> type (read) as you need.
Based on my example above, here's an implementation:
public class LongTransformer implements Transform<Long>
{
// Default value which is set if no / empty input is available
private final long defaultValue;
public LongTransformer(long defaultValue)
{
this.defaultValue = defaultValue;
}
public LongTransformer()
{
this(-1); // Just in case you always have -1 as default
}
#Override
public Long read(String value) throws Exception
{
// If there's no or an empty String the default value is returned
if( value == null || value.isEmpty() == true )
{
return defaultValue; //
}
return Long.valueOf(value); // Return the value
}
#Override
public String write(Long value) throws Exception
{
/*
* Nothing special here. In case you you need a empty string if
* value = -1, you can do it here.
*/
return value.toString();
}
}
And finally a example how to use. The key parts are between the two lines:
final String xml = "<root value=\"\">\n"
+ "</root>";
// ---------------------------------------------------------------------
final LongTransformer numberTransformer = new LongTransformer(-1);
RegistryMatcher m = new RegistryMatcher();
m.bind(long.class, numberTransformer);
Serializer ser = new Persister(m);
// ---------------------------------------------------------------------
Example root = ser.read(Example.class, xml);
System.out.println(root);
Output:
Example{value=-1}
#vandus, here is what I did so far:
I created an adapter in the Main onCreate class, the Model that I pass in as a List is the root node of the XML file:http://www.w3schools.com//xml/simple.xml
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://www.w3schools.com")
.setConverter(new SimpleXMLConverter())
.build();
GitHubService foodService = restAdapter.create(GitHubService.class);
List<BreakfastMenu> repos = foodService.getFile();
repos.toString();
public interface GitHubService
{
#GET("/xml/simple.xml")
List<BreakfastMenu> getFile();
}
Related
I make request directly to VK api with token
Like this: https://api.vk.com/method/groups.get?fields=photo_50&access_token=MY_TOKEN&filter=admin%2C%20editor%2C%20moder&extended=1
Here is spec about api
But I can't serialize the response to object by using Gson, because is response array there is int value:
{
"response": [
2,
{
"gid": 59295,
"name": "Создание",
"screen_name": "book",
"is_closed": 0,
"type": "group",
"photo_50": "https://pp.userapi.com/qwvD6SPkYzo.jpg"
},
{
"gid": 57150,
"name": "Массаж",
"screen_name": "club10450",
"is_closed": 2,
"type": "group",
"photo_50": "https://pp.userapi.com/ZKnmRkS1izs.jpg"
}
]
}
How can I make serialize it to object by using Gson?
Despite you've already resolved the issue by changing the API version via the GET URL parameters, here is a method of dealing with "non-standard" JSONs you might face in the future. I'm assuming you have correct mappings, but the array length (presumably) is put as the very first array element. Gson cannot handle such a special case itself (at least if it expects {...} objects), probably giving you something like this:
Expected BEGIN_OBJECT but was NUMBER at line 3 column 10 path $.response[0]
Assuming you have mappings similar to the next two:
final class ElementsResponse {
#SerializedName("response")
final List<Element> response = null;
}
final class Element {
#SerializedName("gid")
final int gid = Integer.valueOf(0);
#SerializedName("name")
final String name = null;
#SerializedName("screen_name")
final String screenName = null;
#SerializedName("is_closed")
final int isClosed = Integer.valueOf(0);
#SerializedName("type")
final String type = "";
#SerializedName("photo_50")
final URL photo50 = null;
}
You can easily create your type adapter with a special type adapter factory in order to deal with the given JSON:
final class LengthArrayTypeAdapterFactory
implements TypeAdapterFactory {
// The instance holds no state and can be created as a singleton
private static final TypeAdapterFactory lengthArrayTypeAdapterFactory = new LengthArrayTypeAdapterFactory();
private LengthArrayTypeAdapterFactory() {
}
// However, the factory method does not let a caller to create an instance itself, and _may_ create it itself if necessary (encapsulation)
static TypeAdapterFactory getLengthArrayTypeAdapterFactory() {
return lengthArrayTypeAdapterFactory;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Are we dealing with a java.util.List instance?
if ( List.class.isAssignableFrom(typeToken.getRawType()) ) {
// Resolve the list element type if possible
final Type elementType = getElementType(typeToken.getType());
// And request Gson for the element type adapter
final TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
// Some Java boilerplate regarding generics in order not letting the #SuppressWarnings annotation cover too much
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) new LengthArrayTypeAdapter<>(elementTypeAdapter);
return castTypeAdapter;
}
// Or let Gson pick the next downstream type adapter itself
return null;
}
private static Type getElementType(final Type listType) {
// The given type is not parameterized?
if ( !(listType instanceof ParameterizedType) ) {
// Probably the (de)serialized list is raw being not parameterized
return Object.class;
}
final ParameterizedType parameterizedType = (ParameterizedType) listType;
// Or just take the first type parameter (java.util.List has one type parameter only)
return parameterizedType.getActualTypeArguments()[0];
}
private static final class LengthArrayTypeAdapter<E>
extends TypeAdapter<List<E>> {
// This type adapter is designed to read and write a single element only
// We'll take care of all elements array ourselves
private final TypeAdapter<E> elementTypeAdapter;
private LengthArrayTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
#Override
public List<E> read(final JsonReader in)
throws IOException {
// Gson type adapters are supposed to be null-friendly
if ( in.peek() == NULL ) {
return null;
}
// Consume the array begin token `[`
in.beginArray();
// The next value is most likely the array length?
final int arrayLength = in.nextInt();
final List<E> list = new ArrayList<>();
// Read until the array has more elements
while ( in.hasNext() ) {
// And let the element type adapter read the array element so push the value to the list
list.add(elementTypeAdapter.read(in));
}
// Consume the array end token `]`
in.endArray();
assert arrayLength == list.size();
return list;
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final List<E> list)
throws IOException {
if ( list == null ) {
// Must be null-friendly always
out.nullValue();
} else {
// Writing the `[` token
out.beginArray();
// Writing the list size/length
out.value(list.size());
for ( final E element : list ) {
// And just write each array element
elementTypeAdapter.write(out, element);
}
// Finalizing the writing with `]`
out.endArray();
}
}
}
}
So all you had to do could be just adding the type adapter factory to the Gson configuration creating your special arrays-aware Gson:
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getLengthArrayTypeAdapterFactory())
.create();
final ElementsResponse elementsResponse = gson.fromJson(JSON, ElementsResponse.class);
elementsResponse.response.forEach(e -> System.out.println(e.name));
System.out.println(gson.toJson(elementsResponse));
Output:
Создание
Массаж
{"response":[2,{"gid":59295,"name":"Создание","screen_name":"book","is_closed":0,"type":"group","photo_50":"https://pp.userapi.com/qwvD6SPkYzo.jpg"},{"gid":57150,"name":"Массаж","screen_name":"club10450","is_closed":2,"type":"group","photo_50":"https://pp.userapi.com/ZKnmRkS1izs.jpg"}]}
Note that this type adapter factory always assumes that the first array element is a number, and you might need to analyze the elementType if necessary (for example, if it's a java.lang.Number or its subclass).
Resolved, Added param to url v=5.61 version number
{
"response": {
"count": 190,
"items": [{
"id": 28261334,
"name": "TJ",
"screen_name": "tj",
"is_closed": 0,
"type": "page",
"is_admin": 0,
"is_member": 1,
"photo_50": "https://pp.vk.me/...f2c/06crfCSL1KY.jpg"
}]
}
}
I am calling web-services which can have 2 types of json object in response. Now sometimes i get key profile with type String and sometimes it may have same key with type 'ProfileSubObject'. So how to manage this case? Below are my two types of object. I am using Jackson library to parse json.
1.)
{
"data": [
{
"profession": "iOS Developer",
"thanks": {
"count": 5
},
"profile": "test"
}
]
}
2.)
{
"data": [
{
"profession": "iOS Developer",
"thanks": {
"count": 5
},
"profile": {
"val1":"test1",
"val2":"test2"
}
}
]
}
Key profile have 2 different type of object based on web-service call.
Following is my data class structure.
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class DataObject {
#JsonProperty("profession")
private String profession;
#JsonProperty("profile")
private ProfileObject profile;
#JsonProperty("thanks")
private ThanksObject thanks;
public String getProfession() {
return profession;
}
public ThanksObject getThanks() {
return thanks;
}
public ProfileObject getProfile() {
return profile;
}
}
And Profile class is as per below.
public class ProfileObject {
ProfileObject(){
}
ProfileObject(ProfileSubObject profileSubObject){
this.profileSubObject= profileSubObject;
}
ProfileObject(String profile){
this.profile= profile;
}
private ProfileSubObject profileSubObject;
private String profile;
public ProfileSubObject getProfileSubObject() {
return profileSubObject;
}
}
Now when i parse my object, ProfileObject is always null. I want it to get parsed based on proifle key data type.
Anyone could help me with parsing?
In constructing the solution, I faced two problems:
the Json structure does not match a single DataObject
the original problem of deserializing same property into differnt types of Java objects.
The first problem I solved by constructing JavaType objects which tell Jackson the generic type of the collections involved. There are two such collections: a Map, consisting of a single entry with key "data" and value of List of DataObjects
The second problem, I solved with the Jackson feature of #JsonAnySetter which directs Jackson to call a single method for all properties it doesn't recognize. For this purpose, I added #JsonIgnore to the profile variable to make sure that Jackson indeed doesn't recognize it. Now Jackson calls the same method for the two input jsons
This is the new DataObject class:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class DataObject
{
#JsonProperty("profession")
public String profession;
#JsonIgnore // forcing jackson to not recognize this property
public ProfileObject profile;
#JsonProperty("thanks")
public ThanksObject thanks;
public String getProfession() { return profession; }
public void setProfession(String p) { profession = p; }
public ThanksObject getThanks() { return thanks; }
public void setThanks(ThanksObject t) { thanks = t; }
public ProfileObject getProfile() { return profile; }
public void setProfile(ProfileObject p) { profile = p; }
#JsonAnySetter
public void setProfileFromJson(String name, Object value)
{
// if value is single String, call appropriate ctor
if (value instanceof String) {
profile = new ProfileObject((String)value);
}
// if value is map, it must contain 'val1', 'val2' entries
if (value instanceof Map) {
ProfileSubObject profileSubObject =
new ProfileSubObject(((Map<String, String>)value).get("val1"), ((Map<String, String>)value).get("val2"));
profile = new ProfileObject(profileSubObject);
}
// error?
}
}
Here is my test method, which includes the java type construction I mentioned:
public static void main(String[] args)
{
try (Reader reader = new FileReader("C://Temp/xx2.json")) {
ObjectMapper mapper = new ObjectMapper();
// type of key of map is String
JavaType stringType = TypeFactory.defaultInstance().constructType(String.class);
// type of value of map is list of DataObjects
JavaType listOfDataObject = TypeFactory.defaultInstance().constructCollectionType(List.class, DataObject.class);
// finally, construct map type with key and value types
JavaType rootMap = TypeFactory.defaultInstance().constructMapType(HashMap.class, stringType, listOfDataObject);
Map<String ,List<DataObject>> m = mapper.readValue(reader, rootMap);
DataObject do1 = m.values()
// get first (only?) value in map (it is list)
.stream().findFirst().orElse(Collections.emptyList())
// get first (only?) item in list - it is the DataObject
.stream().findFirst().orElse(null);
System.out.println(do1.profile);
System.out.println(do1.profile.profile);
System.out.println(do1.profile.profileSubObject.val1 + " " + do1.profile.profileSubObject.val2);
} catch (Exception e) {
e.printStackTrace();
}
}
This may be of help in regards to parsing JSON, use a JsonReader. It does assume you are using RESTful webservice and have already gotten a HttpURLConnection and an InputStream from the connection.
https://developer.android.com/reference/android/util/JsonReader.html
To make it simple, I have this model:
#Table(name = "Items")
class TItem extends Model {
#Column(name = "title")
private String mTitle;
public String getTitle() { return mTitle; }
public void setTitle(String title) { mTitle = title; }
}
And I'm failing in my testings doing that:
//Create new object and save it to DDBB
TItem r = new TItem();
r.save();
TItem saved = new Select().from(TItem.class).where("id=?", r.getId()).executeSingle();
//Value for saved.getTitle() = null --> OK
r.setTitle("Hello");
r.save();
saved = new Select().from(TItem.class).where("id=?", r.getId()).executeSingle();
//Value for saved.getTitle() = "Hello" --> OK
r.setTitle(null);
r.save();
saved = new Select().from(TItem.class).where("id=?", r.getId()).executeSingle();
//Value for saved.getTitle() = "Hello" --> FAIL
It seems I cannot change a column value from anything to null in ActiveAndroid. Very strange. Is it a bug? I didn't find anything about it, but looks pretty basic this functionallity.
If I debug the app and follow the saving method, the last command it reaches is in SQLLiteConnection.java:
private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
....
// It seems ok, as it is really inserting a null value in the DDBB
case Cursor.FIELD_TYPE_NULL:
nativeBindNull(mConnectionPtr, statementPtr, i + 1);
....
}
I cannot see further, as "nativeBindNull" is not available
Finally I found what happened, and the problem is in ActiveAndroid library.
The null value is saved propertly to DDBB, but is not retrieved correctly. As ActiveAndroid uses cached items, when getting an element, it gets an "old version" and updates it with the new values. Here is where the library fails, because is checking that if not null replace the value, otherwise, nothing.
To solve this, we'll have to change it from the library, in the class Model.java:
public final void loadFromCursor(Cursor cursor) {
List<String> columnsOrdered = new ArrayList<String>(Arrays.asList(cursor.getColumnNames()));
for (Field field : mTableInfo.getFields()) {
final String fieldName = mTableInfo.getColumnName(field);
Class<?> fieldType = field.getType();
final int columnIndex = columnsOrdered.indexOf(fieldName);
....
if (columnIsNull) {
<strike>field = null;</strike> //Don't put the field to null, otherwise we won't be able to change its content
value = null;
}
....
<strike>if (value != null)</strike> { //Remove this check, to always set the value
field.set(this, value);
}
....
}
....
}
This question already has answers here:
How to remove duplicates from a list?
(15 answers)
Closed 8 years ago.
I want to remove duplicates from ArrayList of type Alerts where Alerts is a class.
Class Alerts -
public class Alerts implements Parcelable {
String date = null;
String alertType = null;
String discription = null;
public Alerts() {
}
public Alerts(String date, String alertType, String discription) {
super();
this.date = date;
this.alertType = alertType;
this.discription = discription;
}
}
Here is how I added the elements -
ArrayList<Alerts> alert = new ArrayList<Alerts>();
Alerts obAlerts = new Alerts();
obAlerts = new Alerts();
obAlerts.date = Date1.toString();
obAlerts.alertType = "Alert Type 1";
obAlerts.discription = "Some Text";
alert.add(obAlerts);
obAlerts = new Alerts();
obAlerts.date = Date2.toString();
obAlerts.alertType = "Alert Type 1";
obAlerts.discription = "Some Text";
alert.add(obAlerts);
What I want to remove from them-
I want all alerts which have unique obAlerts.date and obAlerts.alertType. In other words, remove duplicate obAlerts.date and obAlerts.alertType alerts.
I tried this -
Alerts temp1, temp2;
String macTemp1, macTemp2, macDate1, macDate2;
for(int i=0;i<alert.size();i++)
{
temp1 = alert.get(i);
macTemp1=temp1.alertType.trim();
macDate1 = temp1.date.trim();
for(int j=i+1;j<alert.size();j++)
{
temp2 = alert.get(j);
macTemp2=temp2.alertType.trim();
macDate2 = temp2.date.trim();
if (macTemp2.equals(macTemp1) && macDate1.equals(macDate2))
{
alert.remove(temp2);
}
}
}
I also tried-
HashSet<Alerts> hs = new HashSet<Alerts>();
hs.addAll(obAlerts);
obAlerts.clear();
obAlerts.addAll(hs);
You need to specify yourself how the class decides equality by overriding a pair of methods:
public class Alert {
String date;
String alertType;
#Override
public boolean equals(Object o) {
if (this == 0) {
return true;
}
if ((o == null) || (!(o instanceof Alert)))
return false;
}
Alert alert = (Alert) o;
return this.date.equals(alert.date)
&& this.alertType.equals(alert.alertType);
}
#Override
public int hashCode() {
int dateHash;
int typeHash;
if (date == null) {
dateHash = super.hashCode();
} else {
dateHash = this.date.hashCode();
}
if (alertType == null) {
typeHash = super.hashCode();
} else {
typeHash = this.alertType.hashCode();
}
return dateHash + typeHash;
}
}
You can then loop through your ArrayList and add elements if they aren't already there as Collections.contains() makes use of these methods.
public List<Alert> getUniqueList(List<Alert> alertList) {
List<Alert> uniqueAlerts = new ArrayList<Alert>();
for (Alert alert : alertList) {
if (!uniqueAlerts.contains(alert)) {
uniqueAlerts.add(alert);
}
}
return uniqueAlerts;
}
However, after saying all that, you may want to revisit your design to use a Set or one of its family that doesn't allow duplicate elements. Depends on your project. Here's a comparison of Collections types
You could use a Set<>. By nature, Sets do no include duplicates. You just need to make sure that you have a proper hashCode() and equals() methods.
In your Alerts class, override the hashCode and equals methods to be dependent on the values of the fields you want to be primary keys. Afterwards, you can use a HashSet to store already seen instances while iterating over the ArrayList. When you find an instance which is not in the HashSet, add it to the HashSet, else remove it from the ArrayList. To make your life easier, you could switch to a HashSet altogether and be done with duplicates per se.
Beware that for overriding hashCode and equals, some constraints apply.
This thread has some helpful pointers on how to write good hashCode functions. An important lesson is that simply adding together all dependent fields' hashcodes is not sufficient because then swapping values between fields will lead to identical hashCodes which might not be desirable (compare swapping first name and last name). Instead, some sort of shifting-operation is usually done before adding the next atomic hash, eg. multiplying with a prime.
First store your datas in array then split at as one by one string,, till the length of that data execute arry and compare with acyual data by if condition and retun it,,
HashSet<String> hs = new HashSet<String>();
for(int i=0;i<alert.size();i++)
{
hs.add(alert.get(i).date + ","+ alert.get(i).alertType;
}
alert.clear();
String alertAll[] = null;
for (String s : hs) {
alertAll = s.split(",");
obAlerts = new Alerts();
obAlerts.date = alertAll[0];
obAlerts.alertType = alertAll[1];
alert.add(obAlerts);
}
I am getting the most bizzarre behavior with trying to parse an XML, I run through it step by step and all values are assigned and retrieved in order and then the object I create is added to a HashMap for easy look up, the problem is when I am done retrieving it all the HashMap has null values and the ones that aren't null are the value of the very last node that was read, I have walked through it over and over and it all seems correct, but when it's done loading the values in the HasMap look like:
[0] null
[1] NarrationItem#44e9d170
[2] null
[3] null
[4] NarrationItem#44e9d170
etc, etc.
The format of my XML files is:
<narrations>
<narration id="0" name="A" alias="a" >
<line text="This is a test."></line>
</narration>
<narration id="1" name="B" alias="b" >
<line text="This another a test."></line>
</narration>
<narration id="2" name="C" alias="c" >
<line text="This is STILL a test."></line>
</narration>
</narrations>
And my XML parsing method is follows:
public HashMap<String, NarrationItem> NarrationMap = new HashMap<String, NarrationItem>();
private void LoadNarrationsXML() {
NarrationItem i = new NarrationItem();
String line;
String s;
try {
// Get the Android-specific compiled XML parser.
XmlResourceParser xmlParser = this.getResources().getXml(R.xml.narrations);
while (xmlParser.getEventType() != XmlResourceParser.END_DOCUMENT) {
if (xmlParser.getEventType() == XmlResourceParser.START_TAG) {
s = xmlParser.getName();
if (s.equals("narration")) {
i.Clear();
i.ID = xmlParser.getAttributeIntValue(null, "id", 0);
i.Name = xmlParser.getAttributeValue(null, "name");
i.Alias = xmlParser.getAttributeValue(null, "alias");
} else if (s.equals("line")) {
line = xmlParser.getAttributeValue(null, "text");
i.Narration.add(line);
}
} else if (xmlParser.getEventType() == XmlResourceParser.END_TAG) {
s = xmlParser.getName();
if (s.equals("narration")) {
NarrationMap.put(i.Alias, i);
}
}
xmlParser.next();
}
xmlParser.close();
} catch (XmlPullParserException xppe) {
Log.e(TAG, "Failure of .getEventType or .next, probably bad file format");
xppe.toString();
} catch (IOException ioe) {
Log.e(TAG, "Unable to read resource file");
ioe.printStackTrace();
}
}
The NarrationItem object is a custom object defined as:
public class NarrationItem {
int ID;
String Name;
String Alias;
ArrayList<String> Narration = new ArrayList<String>();
public NarrationItem() { }
public void LoadNarration(int id, String name, String alias, ArrayList<String> narration) {
ID = id;
Name = name;
Alias = alias;
Narration.addAll(narration);// = narration;
}
public void Clear() {
ID = 0;
Name = "";
Alias = "";
Narration.clear();
}
}//End Narration
If someone could point out the problem I'd be very thankful I have sat here staring at this issue for hours.
You're only ever creating one NarrationItem object - you're then using a reference to that object as the value for multiple entries in the map. Don't do that. You need to understand that the map doesn't contain an object as the value - it contains a reference to an object.
You can probably fix this just by creating a new NarrationItem each time instead of calling Clear.
It's not clear how you're looking at the map to see those null values, but if you're using the debugger and looking at the internal data structure, you probably shouldn't really be doing that either - instead, step through the keys, values or entries, i.e. stick within the abstraction that HashMap is meant to support.