i am attempting to turn a json string into objects with gson.
I have a very simple example below, and it runs, but the resulting answer is empty, ie: my Answer objects's text field is empty.
import com.google.gson.*;
public class Meow {
public static void main(String[] args) throws Exception{
Gson gson = new Gson();
String jsonOutput = "[{\"answer\":{\"text\":\"text1\"}},{\"answer\":{\"text\":\"text2\"}} ]";
Answer[] a = gson.fromJson(jsonOutput, Answer[].class);
for(Answer i:a) {
System.out.println(i.text);
}
}
public class Answer {
public String text;
public Answer(String text) {
super();
this.text=text;
}
public String toString(){
return text;
}
public void setText(String a){
this.text=a;
}
}
}
Because your JSON doesn't match your class.
Your JSON right now is an array of objects, each containing an answer object as a field.
Your JSON the way you have things would need to look like:
String jsonOutput = "[{\"text\":\"text1\"},{\"text\":\"text2\"}]";
Edit to add from comments:
If you can't change the output, you need a "wrapper". Something like:
public class AnswerWrapper {
public Answer answer;
// etc
}
And use an array of those. That is what the JSON will map to. It can't see them as Answer objects because ... they're not.
One More Edit to Add: Your other option is to write custom deserializers for your classes. I'm a bit mixed on whether you should do this or not, but it will work. The reason I say that is that you have JSON that isn't an array of Answer objects, but you want it to be. I think I'd be annoyed if I came across this in production code because without understanding what was going on it could be confusing.
With that caveat being said, you can create a custom JsonDeserializer and use GsonBuilder:
class AnswerDeserializer implements JsonDeserializer<Answer> {
public Answer deserialize(JsonElement je, Type type,
JsonDeserializationContext jdc)
throws JsonParseException {
return new Answer(je.getAsJsonObject().get("answer")
.getAsJsonObject().get("text").getAsString());
}
}
Then your code would look like:
public static void main(String[] args) throws Exception{
String jsonOutput = "[{\"answer\":{\"text\":\"text1\"}},{\"answer\":{\"text\":\"text2\"}} ]";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Answer.class, new AnswerDeserializer());
Gson gson = gsonBuilder.create();
Answer[] a = gson.fromJson(jsonOutput, Answer[].class);
for(Answer i:a) {
System.out.println(i.text);
}
}
If it were me, and I had JSON that wasn't what I needed it to be but wanted to use GSON to directly serialize/deserialize I'd create the Answer class as a wrapper that hid the details:
/**
* Due to how our JSON is being provided we created an inner
* class.
**/
public class Answer {
private RealAnswer answer;
private class RealAnswer {
public String text;
}
...
}
With the public getters/setters for Answer accessing the private RealAnswer. It just seems way cleaner and easier to understand for the next guy.
Related
I'm working on a project that communicates with an API using JSON. This is my first attempt at JSON and I've been away from java for a few/several years, so please bear with me.
Here is an idea of what the data looks like:
String 1:
[{
"apicall1":
[{
"thisField":"thisFieldData",
"thatField":"thatFieldData",
"anotherField":"anotherFieldData"
}]
}]
String 2:
[{
"apicall2":
[{
"thatField":"thatFieldData",
"someFieldsAreTheSame":"someFieldsAreTheSameData",
"otherFieldsAreNotTheSame":"otherFieldsAreNotTheSame"
}]
}]
As you can see from my data example, the API returns a JSON string that contains the api used. The array inside contains the data. The API's have a lot of data fields in common but they are unrelated beyond that.
EDIT: There are dozens of these API's types that will need to be handled.
What I am trying to do is create a response class that accepts all of the JSON strings and returns an object containing the appropriate data.
For Example:
Gson gson = new Gson(); //Custom TypeAdapter goes here if needed.
Response apicall2 = gson.fromJson(apicall2String, Response.class);
System.out.println(apicall2.thatField); //Prints thatFieldData
System.out.println(apicall2.someFieldsAreTheSame); //Prints someFieldsAreTheSameData
System.out.println(apicall2.otherFieldsAreNotTheSame); //Prints otherFieldsAreNotTheSameData
This is where I am lost. Here is what I have so far. I think I need to use a TypeAdapter here but haven't been able to figure how to apply that to my case.
public class Response { //Change to TypeAdapter possibly?
}
public class apicall1 {
String thisField;
String thatField;
String anotherField;
}
public class apicall2 {
String thatField;
String someFieldsAreTheSame;
String otherFieldsAreNotTheSame;
}
You can use Gson's TypeToken class to deserialize json into object. Below is an example:
JSON:
[{ "apicall1":
[{
"thisField":"thisFieldData",
"thatField":"thatFieldData",
"anotherField":"anotherFieldData"
}]
}]
Model:
class Response{
private List<Result> apicall1;
class Result{
private String thisField;
private String thatField;
private String anotherField;
public String getThisField() {
return thisField;
}
public void setThisField(String thisField) {
this.thisField = thisField;
}
public String getThatField() {
return thatField;
}
public void setThatField(String thatField) {
this.thatField = thatField;
}
public String getAnotherField() {
return anotherField;
}
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
}
public List<Result> getApicall1() {
return apicall1;
}
public void setApicall1(List<Result> apicall1) {
this.apicall1 = apicall1;
}
}
Converter:
public static void main(String[] args) {
String response = "[{ \"apicall1\": [{ \"thisField\":\"thisFieldData\", \"thatField\":\"thatFieldData\", \"anotherField\":\"anotherFieldData\" }]}]";
Gson gson = new Gson();
List<Response> responses = gson.fromJson(response, new TypeToken<List<Response>>(){}.getType());
System.out.println(responses.get(0).getApicall1().get(0).getThisField());
}
I don't know if you want both adapters in one class. Might not be the best OOP design.
To achieve it you would need to do something like so:
public class DoublyTypeAdapter implements JsonDeserializer<ApiCallTypeParent>
{
Gson gson = new Gson();
#Override
public ApiCallTypeParent deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject json = jsonElement.getAsJsonObject();
ApiCallTypeParent desrializeIntoMe;
// Detect which type to implement
if(apiTypeOne(type) {
desrializeIntoMe = new TypeOne();
} else {
desrializeIntoMe = new TypeTwo();
}
for (Map.Entry<String, JsonElement> entry : json.entrySet())
{
switch(entry.getKey()){
case "thisField":
desrializeIntoMe.setThisField(entry.getValue().getAsString());
break;
......
default: // We don't care
break;
}
}
return desrializeIntoMe ;
}
}
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();
}
}
I want to send the server an http request with this json (upper line)
and I want to get such a json and parse it to Java object (lower line)
I remember from last times, that a missing field in a collection that I want to deserialize
crashes the deserialization
(for a single deserialization, if the json has no such field - a default value is inserted)
Is there any way I can create a single Java class to represent both the request json and the two types on response json objects?
My try:
public class ConfigValue {
public String key;
public String defaultValue;
public String value;
}
Gson gson = new Gson();
Type collectionType = new TypeToken<Array<ConfigValue>>() {
}.getType();
ConfigValue[] configValues = (ConfigValue[]) gson
.fromJson(result, collectionType);
Neither of the two JSON strings in your image are directly a list (or array) of ConfigValue objects. They are in fact a JSON object, with one property configValues, which is a list of ConfigValue objects. You therefore need a wrapper class to deserialize them to:
public class ConfigValues {
public ConfigValue[] configValues;
}
public class ConfigValue {
public String key;
public String defaultValue;
public String value;
}
public static void main(String[] args) {
String firstJson = "{\"configValues\":[{\"key\":\"radiusMeters\",\"value\":\"200\"}]}";
String secondJson = "{\"configValues\":[{\"key\":\"redeemExpirationMins\",\"defaultValue\":\"300\"},{\"key\":\"radiusMeters\",\"value\":\"200\",\"defaultValue\":\"400\"}]}";
Gson gson = new Gson();
ConfigValues firstConfigValues = gson.fromJson(firstJson, ConfigValues.class);
ConfigValues secondConfigValues = gson.fromJson(secondJson, ConfigValues.class);
System.out.println(firstConfigValues);
System.out.println(secondConfigValues);
}
If you add toString methods to the two classes, the main method prints the following deserialized objects:
ConfigValues(configValues=[ConfigValue(key=radiusMeters, defaultValue=null, value=200)])
ConfigValues(configValues=[ConfigValue(key=redeemExpirationMins, defaultValue=300, value=null), ConfigValue(key=radiusMeters, defaultValue=400, value=200)])
You can see that any missing fields of ConfigValue are deserialized to null.
I seem to be having trouble creating POJO(Plan Old Java Object) from JSON using GSON. I followed this tutorial to the T but I am still getting a null object. Here's my code:
JSONHandler.java
public class JSONHandler
{
private Gson gson;
private InputStream is;
private Reader reader;
private TripList tripList;
public JSONHandler(InputStream is)
{
this.is = is;
gson = new Gson();
reader = new InputStreamReader(is);
tripList = gson.fromJson(reader, TripList.class);
}
public Gson getGson() {
return gson;
}
public void setGson(Gson gson) {
this.gson = gson;
}
TripList.java
public class TripList
{
#SerializedName("Line")
public String line;
#SerializedName("CurrentTime")
public int currentTime;
public List<Train> Trips;
}
Train.java
public class Train
{
#SerializedName("TripID")
public String tripID;
#SerializedName("Destination")
public String dest;
public List<Prediction> Predictions;
}
Prediciton.java
public class Prediction
{
#SerializedName("StopID")
public int stopID;
#SerializedName("Stop")
public String stop;
#SerializedName("Seconds")
public int seconds;
}
blue.json
{
"TripList":
{
"CurrentTime":1342032950,
"Line":"Red",
"Trips": [
{
"TripID":"R982ECC1E",
"Destination":"Alewife",
"Predictions": [
{"StopID":"70094","Stop":"Ashmont","Seconds":370}
]
},
{
"TripID":"R982ECC78",
"Destination":"Ashmont",
"Note":"Big Red",
"Position":
{"Timestamp":1342032834,"Train":"1809","Lat":42.38725,"Long":-71.11894,"Heading":185},
"Predictions": [
{"StopID":"70067","Stop":"Harvard Square","Seconds":36},
{"StopID":"70069","Stop":"Central Square","Seconds":260}
]
}
]
}
}
The JSON format will follow this paradigm. GSON doesn't throw an error when cannot parse something correctly, it just returns a null value which is irritating. Is there something wrong with the format or the way I handled the java data objects? Any help would be much appreciated
I don't think JSON knows how to read a public List<Train> Trips; as the genericity is compile-time only.
see https://sites.google.com/site/gson/gson-user-guide#TOC-Collections-Examples for more details, but basically, i think you need to specify a TypeToken for you elements
When I ran into this issue we ran up against type erasure - you might want to look into implementing generics (if there will be more List elements in your code) or else explicitly telling Gson what type of type you want to serialize
Try initialising the Lists in each of your model classes.
I.e.
public class TripList
{
#SerializedName("Line")
public String line;
#SerializedName("CurrentTime")
public int currentTime;
public List<Train> Trips = new ArrayList<Train>();
}
There's a trivial problem: You're reading an object containing TripList:
{
"TripList": ...
}
but you tell Gson that you're reading a TripList itself:
tripList = gson.fromJson(reader, TripList.class);
As TripList contains no property named TripList, this does what it does: The unknown properties (i.e., "TripList") get ignored while the missing properties (i.e., all members of "TripList" get left at their default value.
So Gson behaves correctly here. I don't know if there's a problem with generics here. If so, the other answers may help.
Update
There's no other problem there, I've tried it. Just remove the enclosing thing.
Seems like Gson.toJson(Object object) generates JSON code with randomly spread fields of the object. Is there way to fix fields order somehow?
public class Foo {
public String bar;
public String baz;
public Foo( String bar, String baz ) {
this.bar = bar;
this.baz = baz;
}
}
Gson gson = new Gson();
String jsonRequest = gson.toJson(new Foo("bar","baz"));
The string jsonRequest can be:
{ "bar":"bar", "baz":"baz" } (correct)
{ "baz":"baz", "bar":"bar" } (wrong sequence)
You'd need to create a custom JSON serializer.
E.g.
public class FooJsonSerializer implements JsonSerializer<Foo> {
#Override
public JsonElement serialize(Foo foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.add("bar", context.serialize(foo.getBar());
object.add("baz", context.serialize(foo.getBaz());
// ...
return object;
}
}
and use it as follows:
Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, new FooJsonSerializer()).create();
String json = gson.toJson(foo);
// ...
This maintains the order as you've specified in the serializer.
See also:
Gson User Guide - Custom serializers and deserializers
If GSON doesn't support definition of field order, there are other libraries that do. Jackson allows definining this with #JsonPropertyOrder, for example. Having to specify one's own custom serializer seems like awful lot of work to me.
And yes, I agree in that as per JSON specification, application should not expect specific ordering of fields.
Actually Gson.toJson(Object object) doesn't generate fields in random order. The order of resulted json depends on literal sequence of the fields' names.
I had the same problem and it was solved by literal order of properties' names in the class.
The example in the question will always return the following jsonRequest:
{ "bar":"bar", "baz":"baz" }
In order to have a specific order you should modify fields' names, ex: if you want baz to be first in order then comes bar:
public class Foo {
public String f1_baz;
public String f2_bar;
public Foo ( String f1_baz, String f2_bar ) {
this.f1_baz = f1_baz;
this.f2_bar = f2_bar;
}
}
jsonRequest will be { "f1_baz ":"baz", "f2_bar":"bar" }
Here's my solution for looping over json text files in a given directory and writing over the top of them with sorted versions:
private void standardizeFormat(File dir) throws IOException {
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
String path = child.getPath();
JsonReader jsonReader = new JsonReader(new FileReader(path));
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(LinkedTreeMap.class, new SortedJsonSerializer()).create();
Object data = gson.fromJson(jsonReader, Object.class);
JsonWriter jsonWriter = new JsonWriter(new FileWriter(path));
jsonWriter.setIndent(" ");
gson.toJson(data, Object.class, jsonWriter);
jsonWriter.close();
}
}
}
private class SortedJsonSerializer implements JsonSerializer<LinkedTreeMap> {
#Override
public JsonElement serialize(LinkedTreeMap foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
TreeSet sorted = Sets.newTreeSet(foo.keySet());
for (Object key : sorted) {
object.add((String) key, context.serialize(foo.get(key)));
}
return object;
}
}
It's pretty hacky because it depends on the fact that Gson uses LinkedTreeMap when the Type is simply Object. This is an implementation details that is probably not guaranteed. Anyway, it's good enough for my short-lived purposes...