I am working with a JSON response that is improperly formatted. All fields are being returned as Strings. Unfortunately, I have no control over the return data.
I am using Gson and attempting to parse a JSON object that includes a field like this:
{
[...]
"cost": "9.25"
}
It should obviously be printed as a Number. When I try to parse this as a String, Number or double I get a NumberFormatException:
com.google.gson.JsonSyntaxException: java.lang.NumberFormatException:
[...]
at com.myapp.android.LauncherActivity$1.onSuccess(LauncherActivity.java:69)
[...]
Caused by: java.lang.NumberFormatException:
at org.apache.harmony.luni.util.FloatingPointParser.parseDouble(FloatingPointParser.java:267)
at java.lang.Double.parseDouble(Double.java:285)
at com.google.gson.stream.JsonReader.nextInt(JsonReader.java:599)
at com.google.gson.internal.bind.TypeAdapters$7.read(TypeAdapters.java:228)
... 19 more
LauncherActivity Line 69:
Item item = gson.fromJson(response, Item.class);
So I followed this similar question and tried creating a TypeAdapter like so:
public class CostTypeAdapter implements JsonDeserializer<Double>, JsonSerializer<Double> {
public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Double cost;
try {
cost = json.getAsDouble();
} catch (NumberFormatException e) {
cost = 0.00d;
}
return cost;
}
public JsonElement serialize(Double src, Type typeOfSrc,
JsonSerializationContext context) {
return new JsonPrimitive(src);
}
}
And registered it when creating the GsonBuilder:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Cost.class, new CostTypeAdapter());
Gson gson = builder.create();
And my Cost class:
public class Cost {
private Double value;
public Cost(Double value) {
this.value = value;
}
public Double getValue() {
return value;
}
}
But I get the same NumberFormatException.
Any ideas on whats happening here? Shouldn't this exception be caught in my CostTypeAdapter.deserialize(), at the very least?
Any help/guidance is greatly appreciated.
You can also use GsonBuilder's registerTypeAdapter() to catch possible parsing Exceptions and deal with them the way you want.
Example for catching NumberFormatException when parsing Float and make the value null:
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(Float.class, new TypeAdapter<Float>() {
#Override
public Float read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
String stringValue = reader.nextString();
try {
Float value = Float.valueOf(stringValue);
return value;
} catch (NumberFormatException e) {
return null;
}
}
#Override
public void write(JsonWriter writer, Float value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
writer.value(value);
}
});
I ended up having to write a JsonDeserializer for my entire enclosing "Item" class.
public class ItemDeserializer implements JsonDeserializer<Item> {
#Override
public Item deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jobject = (JsonObject) json;
return new Item(
[...],
(jobject.has("cost")) ? jobject.get("cost").getAsDouble() : 0.00d
);
}
}
Would still love to see a solution for my original issue, so I wouldn't have to manually parse every field.
It looks like the cost field in your Item class is declared as an int, not as a double. Changing cost to a double should fix it.
Paul, I have a similar issue of getting numbers in my JSON that are stored as Strings. What's been working for me is this:
public enum Plan {
GUEST_PASS, FREE, PREMIUM;
static Plan fromValue(int value) {
for (Plan plan : plans)
if (value == plan.ordinal())
return plan;
throw new IllegalArgumentException("Invalid value for Plan: " + value);
}
static Plan fromValue(String string) {
try {
return fromValue(parseInt(string));
} catch (IllegalArgumentException _) {
throw new IllegalArgumentException("Invalid value for Plan: " + string);
}
}
private static EnumSet<Plan> plans = EnumSet.allOf(Plan.class);
}
public static class PlanAdapter extends TypeAdapter<Plan> {
#Override public void write(JsonWriter json, Plan plan) throws IOException {
json.value(Integer.toString(plan.ordinal()));
}
#Override public Plan read(JsonReader json) throws IOException {
return Plan.fromValue(json.nextString());
}
}
Seems like you have continuous data in your case, so you would suffice to have a class, I converted to an enum since I had discrete data.
I also ran into a similar situation. I used below adaptor for the conversion. I Found it concise.
.registerTypeAdapter(Double.class, new JsonSerializer<Double>() {
public JsonElement serialize(Double number, Type type, JsonSerializationContext context) {
return new JsonPrimitive(Double.valueOf(number));
}
})
the easiest solution is to make the attribute to be String instead of double and in the get method parse it to double.
for example:
class Location {
private String latitude;
private String longitude;
public double getLatitude() {
return latitude.isEmpty() ? 0d : Double.parseDouble(latitude);
}
public double getLongitude() {
return longitude.isEmpty() ? 0d : Double.parseDouble(longitude);
}
}
Related
I have a class with the following attributes,
public AnalyticsEventProperty(String eventID, String key, Object value, EventPropertyValueType valueType) {
this.eventID = eventID;
this.key = key;
this.value = value;
this.type = valueType();
}
This object is created and passed to an array of event properties, when I do the Json Conversion I get the output below:
{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","name":"app_start","propertyArrayList":[{"eventID":"afc970ef-80cf-4d6e-86e6-e8f3a56f26f5","key":"session_id","value":"69200430-95a0-4e14-9a36-67942917573d"}
I am getting 'key and 'value' used, I can see why, but how do I use the key and values as key and values i.e. "session_id":"69200430-95a0-4e14-9a36-67942917573d", bearing in mind that these key and values may have different property names depending on what is passed in the constructor.
When i create the String i am simply calling
String text_to_send = new Gson().toJson(events);
Where events is the ArrayList.
You can solve this by writing a custom TypeAdapterFactory which obtains the default adapter for your class (that is the reflection based one) and uses it to create an in-memory JSON representation in the form of a JsonObject. That JsonObject can then be modified to have the structure you expect; afterwards it has to be written to the JsonWriter:
class RewritingEventPropertyAdapterFactory implements TypeAdapterFactory {
public static final RewritingEventPropertyAdapterFactory INSTANCE = new RewritingEventPropertyAdapterFactory();
private RewritingEventPropertyAdapterFactory() {}
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only consider AnalyticsEventProperty or subtypes
if (!AnalyticsEventProperty.class.isAssignableFrom(type.getRawType())) {
return null;
}
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
TypeAdapter<JsonObject> jsonObjectAdapter = gson.getAdapter(JsonObject.class);
return new TypeAdapter<T>() {
#Override
public T read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Deserialization is not supported");
}
#Override
public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
// Remove "key" and "value"
String eventKey = jsonObject.remove("key").getAsString();
JsonElement eventValue = jsonObject.remove("value");
// Add back an entry in the form of `"key": "value"`
jsonObject.add(eventKey, eventValue);
// Write the transformed JsonObject
jsonObjectAdapter.write(out, jsonObject);
}
};
}
}
You then have to register the factory with a GsonBuilder.
An alternative would be to perform the complete serialization of the class manually by directly writing the properties to the JsonWriter. This will most likely be a bit more performant, but is also more error-prone.
I have Java class which has String variable member
String sampleNumber="1234";
When I convert that Pojo to JSON using ObjectMapper I get:
{
"sampleNumber":"1234"
}
But I need an integer type in JSON as follow
{
"sampleNumber":1234
}
Can any one help me to get the expected results with using ObjectMapper?
Change property to int
You can change your property to int:
class Pojo {
private int sampleNumber = 1234;
// getters, setters
}
and you will get:
{
"sampleNumber" : 1234
}
Add new getter with right type
If you do not want to change property type to int/Integer you need to ignore getter which returns String and add new getter which will return int/Integer:
class Pojo {
private String sampleNumber = "1234";
public Integer getSampleNumber() {
return Integer.parseInt(sampleNumber);
}
#JsonIgnore
public String getSampleNumberString() {
return sampleNumber;
}
public void setSampleNumber(String sampleNumber) {
this.sampleNumber = sampleNumber;
}
}
For above POJO you will get below JSON:
{
"sampleNumber" : 1234
}
Write custom serialiser which force int
You can write custom serialiser which tries to parse String and write it as Number:
class ForceIntSerializer extends StdScalarSerializer<Object> {
public ForceIntSerializer() {
super(Object.class);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value instanceof String) {
try {
gen.writeNumber(Integer.parseInt(value.toString()));
} catch (NumberFormatException e) {
gen.writeString(value.toString());
}
} else if (value instanceof Integer) {
gen.writeNumber(((Integer) value));
}
}
}
You can use it as below:
class Pojo {
private String sampleNumber = "1234";
#JsonSerialize(using = ForceIntSerializer.class)
public String getSampleNumber() {
return sampleNumber;
}
}
and you will get below JSON:
{
"sampleNumber" : 1234
}
My json string looks like the following:
{
"text": ["foo",1,"bar","2",3],
"text1": "value1",
"ComplexObject": {
.....
}
}
I have a pojo defined like this:
class MyPojo {
List<String> text;
String text1;
ComplexObject complexObject;
}
I use google gson and am able to get my java object populated properly. The problem here is that the field text is an array of mixed types (string and int). So all the entries there are converted into String and i am not able to figure out which entries in the array is a string vs int. I cant use parseInt since the entries in the original array may have "2" as well as 3.
Is there a way for me to get the right instance type of the fields in my array after converting into java object.
SOLUTION
So i implemented the solution using gson the round about way using the JsonDeserializer. And then i tried using jackson. Guess what jackson supports serializing/deserializing the mixed array type by preserving the data types.
ObjectMapper mapper = new ObjectMapper();
MyPojo gmEntry = mapper.readValue(json, new TypeReference<MyPojo >(){});
And i can basically fetch the List<Object> and do an instanceof to check for the datatype.
Shame on you gson!!
By having a custom class and adding a type adapter u can manipulate the string (json.toString() returns with the '"' quotes, so you can see if its a string or not.
Output: (the classes seem correct)
class test.Main$StringPojo pojo{object=foo}
class test.Main$IntPojo pojo{object=1}
class test.Main$StringPojo pojo{object=bar}
class test.Main$StringPojo pojo{object=2}
class test.Main$IntPojo pojo{object=3}
public static void main(final String[] args){
String str = "{\n" +
" \"text\": [\"foo\",1,\"bar\",\"2\",3],\n" +
" \"text1\": \"value1\" }";
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(pojo.class, new JsonDeserializer<pojo>() {
#Override
public pojo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
return new IntPojo(Integer.parseInt(json.toString()));
} catch (Exception e) {
return new StringPojo(json.getAsString());
}
}
});
MyPojo myPojo = builder.create().fromJson(str, MyPojo.class);
for (pojo pojo : myPojo.text) {
System.out.println(pojo.getClass() + " " + pojo.object);
}
}
public static abstract class pojo{
protected Object object;
public pojo() {
}
#Override
public String toString() {
return "pojo{" +
"object=" + object +
'}';
}
}
public static class StringPojo extends pojo{
public StringPojo(String str) {
object = str;
}
}
public static class IntPojo extends pojo{
public IntPojo(int intt) {
this.object = intt;
}
}
public static class MyPojo {
List<pojo> text;
String text1;
}
As you wrote - you defined: List<String> text; but that list also contains integers.
Java is strongly typed, please consider to either declare the List as List<Object> (less preferable) or creating a JSON list that contains only a single type of variable (more preferable).
You can create an abstract class ItemType (for use as array item type) and inherits from it two wrapper classes: one for int type and another for string type.
abstract class ItemType {
protected Object value;
}
class IntType extends ItemType {
IntType(Integer value){
this.value = value;
}
}
class StringType extends ItemType {
IntType(String value){
this.value = value;
}
}
Try this List<ItemType> text;
The above situation can be achived by using TypeAdapter of Gson API.
Please follow : https://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Collection-with-Objects-of-Arbitrary-Types
Not sure if this is what you need, but this is the code I use for parsing JSON.
static public void newsParser(String urlString, String targetObject) throws FileNotFoundException, IOException
{
URL url = new URL(urlString);
JSONParser parser=new JSONParser();
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
Object obj;
try
{
obj = parser.parse(br);
//JSONObject jsonObject = (JSONObject) obj;
JSONArray jsonArray = (JSONArray) obj;
Iterator<?> i = jsonArray.iterator();
while (i.hasNext())
{
slide = (JSONObject) i.next();
newsInfo = (String)slide.get(targetObject);
System.out.println(newsInfo);
newsTitles.add(newsInfo);
}
}
catch (ParseException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
We need to process some broken JSON from a legacy server here that wrongly encodes null values as literal "null" strings in its output.
I already found that I probably want to override https://github.com/FasterXML/jackson-core/blob/master/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java#L368 to "fix" this, but this seems to be so deep inside Jackson that I'd rather do it differently. Are there alternatives, for example by using the ObjectMapper to add a custom deserializer for the String.class or am I lost?
Ok, it worked by overriding the standard String deserializer. Unfortunately I had to copy the complete implementation over because org/codehaus/jackson/map/deser/std/StringDeserializer.java is final and cannot be extended.
public class FixesModule extends SimpleModule {
public FixesModule() {
super();
addDeserializer(String.class, new CustomStringDeserializer());
}
}
and
public class CustomStringDeserializer extends StdScalarDeserializer<String> {
private static final String NULL_STRING = "null";
public CustomStringDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonToken curr = jp.getCurrentToken();
// Usually should just get string value:
if (curr == JsonToken.VALUE_STRING) {
// BEGIN NULL_STRING fix
if (NULL_STRING.equals(jp.getText())) {
return null;
}
// END NULL_STRING fix
return jp.getText();
}
// [JACKSON-330]: need to gracefully handle byte[] data, as base64
if (curr == JsonToken.VALUE_EMBEDDED_OBJECT) {
Object ob = jp.getEmbeddedObject();
if (ob == null) {
return null;
}
if (ob instanceof byte[]) {
return Base64Variants.getDefaultVariant().encode((byte[]) ob, false);
}
// otherwise, try conversion using toString()...
return ob.toString();
}
// Can deserialize any scalar value, but not markers
if (curr.isScalarValue()) {
return jp.getText();
}
throw ctxt.mappingException(_valueClass, curr);
}
// 1.6: since we can never have type info ("natural type"; String, Boolean,
// Integer, Double):
// (is it an error to even call this version?)
#Override
public String deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException {
return deserialize(jp, ctxt);
}
}
I have a simple Json structure like:
{"MessageType":"TimeData","TimeData":{"hh":12,"mm":13,"ms":15,"ss":14}}
and I devised the following classes to deserialize it:
public class JsonMessage
{
public enum MessageTypes{
WhoAreYou,
TimeData
}
JsonMessage(){
}
public MessageTypes MessageType;
}
class TimeData extends JsonMessage{
int hh;
int mm;
int ss;
int ms;
TimeData() {
}
}
I need to split deserialization into tow phases:
1- deserialize to read the MessageType.
2- proceed with the rest of deserialization based on the MessageType
The code is straightforward:
public void dispatch(Object message, IoSession session)
{
Gson gson = new Gson();
JsonMessage result = gson.fromJson(message.toString(), JsonMessage.class);
System.out.println(result.MessageType.toString());
switch (result.MessageType)
{
case WhoAreYou:{
//.....
break;
}
case TimeUpdate:
TimeData res = new Gson().fromJson(message.toString(), TimeData.class);
System.out.println(res.hh);
break;
default:break;
}
}
My Program can enter the correct switch-case(which is TimeUpdate) but it doesn't parse it correctly (The println prints 0 instead of 12)
where do you think I have done something wrong?
thank you
The issue is that your JSON represents an Object that contains another object you're interested in while your Java is just a single object.
You can actually just write deserializers for each type and use them once you determine the MessageType:
public static void main(String[] args)
{
Gson gson = new GsonBuilder().registerTypeAdapter(TimeData.class, new TimeDataDeserializer()).create();
String json = "{\"MessageType\":\"TimeData\",\"TimeData\":{\"hh\":12,\"mm\":13,\"ms\":15,\"ss\":14}}";
JsonMessage message = gson.fromJson(json, JsonMessage.class);
switch(message.MessageType)
{
case TimeData:
TimeData td = new GsonBuilder()
.registerTypeAdapter(TimeData.class, new TimeDataDeserializer())
.create()
.fromJson(json, TimeData.class);
td.MessageType = message.MessageType
System.out.println(td.hh);
break;
default:
break;
}
}
class TimeDataDeserializer implements JsonDeserializer<TimeData>
{
#Override
public TimeData deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
JsonObject jo = je.getAsJsonObject().getAsJsonObject("TimeData");
Gson g = new Gson();
return g.fromJson(jo, TimeData.class);
}
}
I managed to solve this similar problem by implementing a custom JsonDeserializer in the following way.
First you attach to your enum the subclasses based on the type and a method to retrieve the correct Class<?> according to the enum name:
enum MessageType {
WHO_ARE_YOU(WhoAreYou.class),
TIME_UPDATE(TimeUpdate.class);
public final Class<?> clazz;
MessageType(Class<?> clazz) { this.clazz = clazz; }
public static MessageType forName(String name) {
for (MessageType t : values())
if (name.equals(t.name()))
return t;
return NULL;
}
}
Then in the deserialize method I did the following:
public JsonMessage deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
JsonObject object = json.getAsJsonObject();
String kind = object.get("messageType").getAsString();
Class<?> clazz = MessageType.forName(kind).clazz;
JsonMessage result = null;
try {
result = (JsonMessage)clazz.newInstance();
Field[] fs = clazz.getFields();
for (Field f : fs) {
Object value = context.deserialize(object.get(f.getName()), f.getType());
if (value != null)
f.set(result, value);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
Everything is managed by reflection so that a correct object is created and then all fields are deserialized accordingly.
I had a complex hierarchy of objects so I preferred to go this way to let the gson deserializer manage everything. Of course you will need to register the serializer with the gson parser instance.
A NOTE: Your naming of things is quite incorrect according to Java standards. enum constants should be ALL_CAPITALIZED, enum class names should be singular (eg. MessageType). Instance variables should be camelcased (eg. messageType not MessageType)