Convert String date to Object yields Invalid time zone indicator '0' - java

I got an Android app which receives Json responses from a web service. One of the responses is a json string with a date inside. I get the date in the form of a number like "1476399300000". When I try to create an object with it using GSON I get this error:
Failed to parse date ["1476399300000']: Invalid time zone indicator '0' (at offset 0)
Both sides are working with java.util.Date
How can I fix this issue?

The value 1476399300000 looks like ms from the Unix epoch beginning. Just add a type adapter to your Gson:
final class UnixEpochDateTypeAdapter
extends TypeAdapter<Date> {
private static final TypeAdapter<Date> unixEpochDateTypeAdapter = new UnixEpochDateTypeAdapter();
private UnixEpochDateTypeAdapter() {
}
static TypeAdapter<Date> getUnixEpochDateTypeAdapter() {
return unixEpochDateTypeAdapter;
}
#Override
public Date read(final JsonReader in)
throws IOException {
// this is where the conversion is performed
return new Date(in.nextLong());
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final Date value)
throws IOException {
// write back if necessary or throw UnsupportedOperationException
out.value(value.getTime());
}
}
Configure your Gson instance:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(Date.class, getUnixEpochDateTypeAdapter())
.create();
Gson instances are thread-safe as well as UnixEpochDateTypeAdapter is, and can exist as one instance globally. Example:
final class Mapping {
final Date date = null;
}
final String json = "{\"date\":1476399300000}";
final Mapping mapping = gson.fromJson(json, Mapping.class);
System.out.println(mapping.date);
System.out.println(gson.toJson(mapping));
would give the following output:
Fri Oct 14 01:55:00 EEST 2016
{"date":1476399300000}
Note that the type adapter is configured to override the default Gson date type adapter. So you might need to use a more complicated analysis to detect whether is just ms of the Unix epoch. Also note, that you could use JsonDeserializer, but the latter works in JSON-tree manner whilst type adapters work in the streaming way that's somewhat more efficient probably not accumulating intermediate results.
Edit:
Also, it may look confusing, but Gson can make value conversions for primitives. Despite your payload has a string value, JsonReader.nextLong() can read a string primitive as a long value. So the UnixEpochDateTypeAdapter.write should be out.value(String.valueOf(value.getTime())); in order not to modify JSON literals.
Edit
There's also a shorter solution (working with JSON in-memory trees rather than data streaming) which is simply:
final Gson builder = new GsonBuilder()
.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
public Date deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
return new Date(jsonElement.getAsJsonPrimitive().getAsLong());
}
})
.create();

Related

Java - Gson not working with Object defined on the fly

I would like not to define an extra type just to make the json conversion. I am using a library that needs an object as an input and then performs http operations with this data, so I cannot use a hard coded json string as input.
private static final Gson GSON = new Gson();
public static void main(String[] args) {
System.out.println(GSON.toJson(new Object() {
private String email_address = "me#mail.eu";
public String getEmail_address() {return "me#mail.eu"; }
public void setEmail_address(String mail) {email_address = mail; }
}));
}
I tried to remove getter and setter or leave the getter and remove the field but it doesn't work. Anybody knows how to fix this?
Libraries for Json serialization/deseralization like Gson, count on the fact that you have defined your custom object on which you will map the json string. This is because they use reflection on the class to map the fields with the corresponding keys in the json. Without it, it is difficult that they can achieve anything(usable).
Why not define an extra type ? We are not in the 1980s. I would personnally use a DTO. What is Data Transfer Object?
But maybe the answer to you question reside here : Java - Does Google's GSON use constructors?

Find out if class field annotated by Gson SerializedName actually existes in Json

I'm using google Gson to parse json and create an appropriate object:
public class Settings {
#SerializedName("version")
public String version = "1";
#SerializedName("ad_native_enabled")
public boolean nativeAdEnabled = false;
}
The problem is that I need to know if the value of nativeAdEnabled is actually parsed from json or it's the default value specified by me, i.e. does the ad_native_enabled key exist in json, or not? I've tried to use Boolean instead of boolean and just do null check, but Gson deserialisation failed. Here is the snippet of my json:
{
"status": "success",
"ad_native_enabled": false,
}
DISCLAIMER
In my situation it's not relevant and elegant to parse the json by hand and do the detection on that level(and I guess in this case I have to priorly owe the list of the keys the existence of which I want to check). It's highly desirable to somehow infer the needed information on the object level.
I described a problem on the example of a boolean, but the question may be generalised and may refer to all primitive types. So it'd be great to have a generic solution for this problem.
I know you said you already tried this, but using a Boolean field should work. I have reduced your example down a bit, and it works as expected.
I defined the Settings class like this:
public static class Settings {
#SerializedName("ad_native_enabled")
public Boolean nativeAdEnabled;
}
If you then parse JSON that contains the field:
String json = "{\"ad_native_enabled\": false}";
Settings settings = gson.fromJson(json, Settings.class);
System.out.println(settings.nativeAdEnabled); // prints false
Whereas if you parse JSON that does not contain the field:
String emptyJson = "{}";
Settings emptySettings = gson.fromJson(emptyJson, Settings.class);
System.out.println(emptySettings.nativeAdEnabled); // prints null
Did you perhaps leave the default value of the field as false? If so, the second example will print false as well. Also it seems GSON does not particularly like trailing commas after the last property in JSON objects - maybe that was why you were getting errors?
After seeing your comment, I thought a bit more about whether it is possible to somehow support default values, while still being able to tell whether the field was present in the JSON or not. The best solution I could come up with was to introduce a new wrapper type, with a custom deserializer.
I started by defining this wrapper type, which just contains the actual value of the field, and an indicator of whether this value is the default value or not:
public static class ValueWrapper<T> {
public final T value;
public final boolean isDefaultValue;
public ValueWrapper(T value, boolean isDefaultValue) {
this.value = value;
this.isDefaultValue = isDefaultValue;
}
}
The Settings class then looks like this:
public static class Settings {
#SerializedName("ad_native_enabled")
public ValueWrapper<Boolean> nativeAdEnabled = new ValueWrapper<>(false, true);
}
Here I have defined the field as having value false by default, which is why isDefaultValue is set to true.
I then defined a custom deserializer for this type. The basic idea is to take the type of ValueWrapper you are trying to deserialize, extract its generic parameter, deserialize the actual value in the JSON as the generic parameter type, and then return a new ValueWrapper where isDefaultValue is set to false. This deserializer looks like this:
public static class ValueWrapperDeserializer implements JsonDeserializer<ValueWrapper<?>> {
#Override
public ValueWrapper<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
ParameterizedType parameterizedType = (ParameterizedType) typeOfT;
Type valueType = parameterizedType.getActualTypeArguments()[0];
Object value = context.deserialize(json, valueType);
return new ValueWrapper<>(value, false);
}
}
Now all we need to do is register the custom deserializer:
Gson gson = new GsonBuilder()
.registerTypeAdapter(ValueWrapper.class, new ValueWrapperDeserializer())
.create();
And we can then run through my two examples from above:
String json = "{\"ad_native_enabled\": false}";
Settings settings = gson.fromJson(json, Settings.class);
System.out.println(settings.nativeAdEnabled.value); // prints false
System.out.println(settings.nativeAdEnabled.isDefaultValue); // prints false
String emptyJson = "{}";
Settings emptySettings = gson.fromJson(emptyJson, Settings.class);
System.out.println(emptySettings.nativeAdEnabled.value); // prints false
System.out.println(emptySettings.nativeAdEnabled.isDefaultValue); //prints true
So this allows us to have the default value, but still be able to tell whether the field was set or not using isDefaultValue. This obviously has quite an impact on the API to the user of the Settings object, but perhaps this is neater than handling nulls and storing the default values elsewhere.

Gson parse unquoted value

I using in my project GSON library, everything is fine, but now i'm stuck with a problem, where i need to use a custom deserializer on unquoted values.
I have the following value, and need to parse from json:
[ ["county","=", field_name], ["name", "ilike", "username"] ]
I need to parse unquoted values with a custom deserializer, to a wrapper class like:
public class StringField {
private String value;
public String getValue() {
return value;
}
}
And value will have "field_name" as string.
The problem is that the data is not valid JSON.
JSON does not permit such "unquoted value" strings such as field_name and neither does Gson. Either fix the input such that it is valid JSON (perhaps "$field_name$") - or use a tool (i.e. not Gson) that can cope with non-JSON text that resembles JSON.
This situation can't be corrected with Custom Deserialization because the data isn't even parsed correctly to Json tokens: Gson will throw an exception as the invalid/non-JSON is encountered.
At the very least this would require creating a customized JsonReader implementation that can read "barewords" as strings. However, this is problematic to do because JsonReader does not conform to any specialized interfaces (so it must be subclassed, oops!) and is final (so it can't be subclassed, oops!). As such, unless willing to edit the Gson library source: not possible.
With the below code, I parsed your JSON without problems, I left Gson decide how to parse it, except assuming it contained a List outermost. And the result was a List of Lists of Strings. I did not understand very well why you need StringField class.
package stackoverflow.questions;
import java.util.List;
import com.google.gson.*;
public class Q20557131 {
public static void main(String[] args){
String json = "[[\"county\",\"=\", field_name], [\"name\", \"ilike\", \"username\"]]";
Gson g = new Gson();
List outerList = g.fromJson(json, List.class);
List innerList = (List) outerList.get(0);
for(Object o: innerList)
System.out.println(o.getClass());
}
}
By default, Gson 2.2.4 is lenient, even if has the lenient property set to false, from documentation
Configure this parser to be be liberal in what it accepts. By default, this parser is strict and only accepts JSON as specified by RFC 4627. Setting the parser to lenient causes it to ignore the following syntax errors:
....
Strings that are unquoted or 'single quoted'.
...
even if documentation states that property is false by default, in the source code of the JsonReader#fromJson:
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
reader.setLenient(true); <-- always true
try {
reader.peek();
isEmpty = false;
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
return object;
} catch (EOFException e) {
...
I've solved this problem years ago in another way (sorry for delayed). Wrote symbolic preprocessor class, which replace by regexp labels like field_name with actual values from model and then parsed json.

Gson Long lost data when parse from String

I have json string represenatation of some object
class objects is
public class SMPBBaseObjectsList {
public ArrayList<Object> data = new ArrayList<>();
public Integer count;
public Integer limitFrom;
public Integer limitTo;
public Boolean hasMore;
public String dataItemsClass;
}
And i have json
{"classItem":"smpb.utility.classes.SMPBBaseObjectsList","dataItemsClass":"smpb.base.classes.SMPBUser","dataSliceCode":"012013","data":[{"id":1374046117510970000,"Name":"Test3","classItem":"smpb.base.classes.SMPBUser","dataSliceCode":"012013"}],"filter":{"orderItems":[],"filterItems":[]}}
I try parse this json and create object of my class with next code:
String json = "{\"classItem\":\"smpb.utility.classes.SMPBBaseObjectsList\",\"dataItemsClass\":\"smpb.base.classes.SMPBUser\",\"dataSliceCode\":\"012013\",\"data\":[{\"id\":1374046117510970000,\"Name\":\"Test3\",\"classItem\":\"smpb.base.classes.SMPBUser\",\"dataSliceCode\":\"012013\"}],\"filter\":{\"orderItems\":[],\"filterItems\":[]}}";
SMPBBaseObjectsList list = new GsonBuilder().create().fromJson(json, SMPBBaseObjectsList.class);
System.out.println("BEFORE:" + json);
System.out.println("AFTER: " + list);
System outputs:
BEFORE:{"classItem":"smpb.utility.classes.SMPBBaseObjectsList","dataItemsClass":"smpb.base.classes.SMPBUser","dataSliceCode":"012013","data":[{"id":1374044905885298000,"Name":"Test3","classItem":"smpb.base.classes.SMPBUser","dataSliceCode":"012013"}],"filter":{"orderItems":[],"filterItems":[]}}
AFTER: {"classItem":"smpb.utility.classes.SMPBBaseObjectsList","dataItemsClass":"smpb.base.classes.SMPBUser","dataSliceCode":"012013","data":[{"Name":"Test3","id":1.374044905885298011E18,"classItem":"smpb.base.classes.SMPBUser","dataSliceCode":"012013"}],"filter":{"orderItems":[],"filterItems":[]}}
As u can see in Json String i have ID with value 1374044905885298000 , but when object serialized from string i got 1.374044905885298011E18
And problem is what this representation of Long lost last zeros 0000 and i got Long 1374044905885297920
Why? and how fix it?
Data in Array is String map, and it's already all Long id Double format.
I try registerAdapater for Long or Double but never triggered.
Version of Gson 2.2.4
UPDATE
It's not duplicate of question
How to prevent Gson from converting a long number (a json string ) to scientific notation format?
I can't tell exactly what the problem is, but you can solve it by creating another class, i.e. Data to use in the List instead of the Object class... I tried this code and it's working fine for me!
So, you need to replace the ArrayList<Object> in your SMPBBaseObjectsList by:
public ArrayList<Data> data = new ArrayList<>()
And create a new class like this:
public class Data {
public Long id;
public String Name;
public String classItem;
public String dataSliceCode;
}
I guess there's an issue when parsing the JSON to an Object object, it probably makes some conversion that leads to that number formatting, but unfortunately I'm not an expert in this Java low-level issues...
Anyway, with this code you are explicitly specifying that you want that value parsed into a Long, so there's no problem!

Using GSON to parse an object with mixed types

I'm using a webservice which unfortunately I don't have any control over, there is one element called price that can have 2 types of values, it can either be a double:
price: 263.12
or a string with a specific format:
price: "263.12;Y"
In the second case the ;N indicates that the price can be modified (ie: a discount can be added), I tried to convince the developers of the service to modify the response and send the Y or N (depending on the case) in a separate value (discount: "Y" | "N:), but they said that for now they won't do it.
Within the POJO I declared for this case, I have the following case:
private float precio;
public void setPrice(String value){
if(value.indexOf(";") == -1){
price = Float.parseFloat(value);
} else {
String[] p = value.split(";");
price = Float.parseFloat(p[0]);
}
}
public float getPrice(){return price;}
But unfortunately using:
Product obj = new Gson().fromJson(response, Product.class);
Never actually cals the setter, in the cases where the price is set as a proper double it works just fine, but where I'm receiving the string it just crashes, any suggestions on how this could be handled, worst case scenario I could create a second POJO and try/catch the object creation, but there should be a better idea and searching so far has yielded no results.
You could implement a TypeAdapter that overwrites the default serialization. You have to register that TypeAdapter for a certain class ...
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Product.class, new ProductAdapter());
Gson gson = builder.create();
... so this way any members of type Product ...
String jsonString = gson.toJson(somethingThatContainsProducts);
... will be handled by the TypeAdapter:
public class ProductAdapter extends TypeAdapter<Product> {
public Product read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
String json = reader.nextString();
// convert String to product ... assuming Product has a
// constructor that creates an instance from a String
return new Product(json);
}
public void write(JsonWriter writer, Product value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
// convert Product to String .... assuming Product has a method getAsString()
String json = value.getAsString();
writer.value(json);
}
}
Check out the Google GSON documentation for more.
Hope this helps ... Cheers!
You could write a TypeAdapter or JsonDeserializer.
You can also just rely on the fact that Gson will massage types for you and go the other way with your type:
class Pojo { String price; }
...
String json = "{\"price\":1234.5}";
Pojo p = new Gson().fromJson(json, Pojo.class);
System.out.println(p.price);
produces:
1234.5
When you want to access/get price as a double , convert it appropriately in the getter.

Categories

Resources