I need to know if there is an easier way to serialize a single element Json array as a simple string using Gson. The requirements I have is this: I have an object which is an array of strings. If the array contains only one element, when it gets serialized into JSON, I don't want it represented as an array - I want it represented as a string. So, for example, if this array contains 2 elements, it should look like this:
[
"Something",
"Nothing"
]
If the array contains only one element, it should be rendered like this:
"Something"
The code below is a sample class that demonstrates the solution I found. If you leave all of the code un-commented, you will get a 2 element array correctly rendered in Json. If you comment out the following line you will get a single string:
testArray.add("Nothing");
The problem that I have with this solution is that it seems "clunky". I have to create a separate object of "Object" to make this work, and this seems unnecessary. I have also read that it is possible to do this using a custom serializer, but this seems like a lot of code too for such a small thing. Is there something built-in to Gson (or another library) which can do what I am trying to do?
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
public class DemoClassSerialize {
public static void main(String[] args) {
Object testObject = new Object();
JsonArray testArray = new JsonArray();
testArray.add("Something");
//comment out the next line to test the output when there is only one element in this array
testArray.add("Nothing");
if (testArray.size()==1) {
testObject=testArray.get(0);
} else {
testObject=testArray;
}
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
String gsonString=gson.toJson(testObject);
System.out.println(gsonString);
}
}
Related
I have created a Class that has a constructor that takes a string line and after some processing of the data turns it into an object.
I need to feed data from a CSV one line at a time into the constructor to create an object for every line of the file. However from all my searching I cannot figure out how to create these objects as,from what I have learnt, to create object you have to name these objects. Is there a way to create an array of objects so I don't have to name each object? for example the first line would be like Object[0] and so on?
public class Object{
String Name, Example, Example2;
Object(String data){
//Data manipulation to get into Name Example and Example2 creating an
//object
}
public String getName{
return Name;
}
}
public class ObjectFeed{
//This is where I would open the file and feed it line by line into the
//object class
}
I should be able to use the getter methods i created for the Object class on any line number and it should get that information. I just do not understand how to feed data into Objects and create multiple objects.
I would do something like this:
public void createYourObjects() {
List<String> lines = // however you are reading lines from the CSV file
List<YourObject> objects = new ArrayList<>();
for(String line in lines) {
objects.add(methodB(line);
}
}
public YourObject createYourObjectFrom(String line) {
List<String> columns = line.spilt(",");
return new YourObject(columns.get(0), columns.get(1) ..... columns(n-1)); // where n is the size of the list n-1 is the index of the last item in the list
}
Its bit pseudocode-ish but I think it illustrates the basic idea. By using a separate method to construct your object you separate the concern, isolating the creation of your list of objects from the structure of the CSV file.
It also gives you a place to provide any error or special case handling you may need.
In a real world scenario you may need to handle different types of data, translating from String to say int, and also when a column is empty.
In the case where a column is empty you will get a smaller list of columns, and you will have to handle this.
If the data is coming from a CSV, each cell should be separated by a comma, so you should be able to take the input string data and split it.
This could be done like:
String[] csvList = data.split(",");
You can then assign each element of csvList as one of your object's properties, and loop through all of its elements to produce a list of your objects.
List<YourObject> objects = new List<YourObject>();
// increment i by the how many properties YourObject has
for (int i = 0; i < csvList.length; i += 2) {
YourObject obj = new YourObject();
obj.FirstProperty = csvList[i];
obj.SecondProperty = csvList[i+1];
...
objects.add(obj);
}
Using the below method i could get the "id" element(first element in first list) value. i need to get that element value but based on the condition of another element "gifts.nameEn"
Response response = request.post("/pop/transaction/inquiry");
JsonPath jsonPathEvaluator = response.jsonPath();
List<HashMap> activeCampaignsList = jsonPathEvaluator.getList("");
for(int i=0; i< activeCampaignsList.size(); i++)
{
offerId = activeCampaignsList.get(i).get("id").toString();
break;
}
And Here's the response snippet so you would be aware of what exactly i'm looking for:
[ { "**id**": 1222, "type": "WITHIN_TRANSACTION", "gifts": [ { "startDate": "2019-06-26", "endDate": "2019-06-26", "id": 26130, "nameAr": "abcd201957261140057", "nameEn": "*abcd201957261140057*",
RestAssured allows you use JsonPath which has a strong utility in JSON processing. Instead of working with paths, you can convert JSON to Java Objects and I will show you exactly how to do it.
So, your JSON starts with an array of elements. Each of the element is JSON Object. Each of the JSON Object contains nested JSON Array gifts with JSON Objects in it. Let me explain further.
We have to process the array first, the start of your JSON [. In order to do that we can create a class representing this Array like this:
public class ElementObject { //this name is irrelevant. You can call it whatever you want
public String id; //the names of the variables are relevant
public String type; //each of variable name corresponds to the type and name in the JSON
public List<GiftObject> gifts; //here we handle the nested JSON Array of JSON Objects
}
Now, we require an object to handle gifts. That will require another class:
public class GiftObject { //irrelevant name
public String startDate; //relevant name
public String endDate;
public String id;
public String nameAr;
public String nameEn;
}
Now, all we have to do is call 1 method from JsonPath to parse whole JSON into Java classes. Like this:
response.jsonPath().getObject("", ElementObject[].class);
That's mostly it. Starting from the top:
[
{
"id":1222,
"type":"WITHIN_TRANSACTION",
We have an JSON Array with JSON Objects. 1 JSON Object equals 1 instance of ElementObject class. All of the objects from the Array will be parsed into ElementObject[].class and JsonPath will create an instance using Java Reflection (You don't have to worry about that).
"gifts": [ { "startDate": "2019-06-26", "endDate": "2019-06-26", "id": 26130, "nameAr": "abcd201957261140057", "nameEn": "abcd201957261140057",
Each of the gifts will be parsed into new instance of GiftObject and storred in List<GiftObject> in ElementObject class.
All we have to do now is work on our Java classes.
To fully answer your question - you need ID value based on gifts.nameEn
for (ElementObject element : elements) {
for (GiftObject gift : element.gifts) {
if (gift.nameEn.equals("abcd201957261140057")) {
System.out.println(element.id);
}
}
}
The above for loop can be easily replaced with a method, or Java Streams. Whatever suits you the most.
Given the above code the results will be:
1222
Hope it helps!
EDIT:
If you get the Exception like this Cannot deserialize object because of no JSON deserializer found in classpath. Please put either Jackson (Databind) or Gson in the classpath. all you have to do is add jackson-databind to Maven. Rest Assured is able to use Gson or Jackson databind to transform JSON into Java classes. All you need is the dependency, and you are all set.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
Original code from here.
Looking at the code below (ignoring import statements):
public class JsonTest2 {
public static void main(String[] args){
Gson gson = new Gson();
JsonParser jsonParser = new JsonParser();
JsonReader reader = null;
try {
reader = new JsonReader(new InputStreamReader(new
FileInputStream("./twitterUser.json")));
}
catch (IOException e) {
}
JsonArray userArray = jsonParser.parse(reader).getAsJsonArray();
List<TwitterUser> twitterUsers = new ArrayList<TwitterUser>();
//For each element in the JSON array, create a new TwitterUser
//and populate with the json data:
for ( JsonElement aUser : userArray ) {
TwitterUser aTwitterUser =
gson.fromJson(aUser, TwitterUser.class);
twitterUsers.add(aTwitterUser);
}
//For each new TwitterUser that was created, print them out:
for ( TwitterUser tUser : twitterUsers) {
System.out.println(tUser);
}
}
}
The array List twitterUsers = new ArrayList(); gets created and contains two instances of TwitterUser.
How do I reference each instance individually (what is their variable name)? If this is not possible e.g. you can only use TwitterUser[0], how can I use JSON to create say five objects each with individual variable names?
Also a bit of explaining-around the subject is probably needed...
A List is not an array. An ArrayList is a List that is backed by an array (that you can't access directly). Instead you use the List interface to
List.get(int) which Returns the element at the specified position in this list.
Use the List.iterator() to iterate the List, perhaps indirectly with a for-each loop.
So, for the first case
TwitterUser tu = twitterUsers.get(0); // <-- get the first TwitterUser
// in the List.
or for the second
for (TwitterUser tu : twitterUsers) {
// do something with each TwitterUser in tu
}
How do I reference each instance individually
As twitterUsers[0] and twitterUsers[1].
(what is their variable name)?
They aren't variables, and they don't have names.
If this is not possible e.g. you can only use TwitterUser[0], how can I use JSON to create say five objects each with individual variable names?
You can't. Variable names things that you write in your source code ... before it is compiled. A library class (such as the JSON library you are using) can only do things at runtime ... after the source code is compiled.
But if you really want different variables, then it is easy to write your code to do this, provided you know exactly how many variables are required; e.g.
TwitterUser userOne = twitterUsers[0];
TwitterUser userTwo = twitterUsers[1];
Obviously, this is hard-wiring the assumption that there are (at least) two elements in the JSON array you will be receiving.
Is this a good idea? Probably not:
It doesn't scale.
If the number of variables to be populated is not constant, your code gets rather complicated and fragile. (You can initialize them to null, but you then have to deal with possible nulls each time you use the variables!)
You are hard-wiring assumptions that typically shouldn't be hard-wired. (At a minimum you should check that the number of elements in the JSON array matches your expectations ... and treat an incorrect assumption as an error!)
But in some circumstances, it could make the code easier to read; e.g. if the named variables (userOne and userTwo) are going to be used extensively, or they have more descriptive names.
What about using a Map instead of variables? It does scale (sort of) but you still have problems when the number of entries in the array is variable. And the code is more complicated.
I have written some code that creates an array list of json data using the Gson library which looks something like this.
{
"filename": "somefile.mp3"
"artist": "artist1"
"title": "title1"
"genre": "rock"
}
{
"filename": "anotherfile.mp3"
"artist": "artist2"
"title": "title2"
"genre": "electro"
}
Im trying to figure out how I can sort this array list by the artist element. Currently its just ordering it in the way it is created originally via the filename. Reading on stackoverflow suggests I should be trying to use a custom comparator. Ive been looking at this answer (How to sort GSON Array based on a key?) but its not working for me.
I have a custom class called Tracks.class which has all the getters and setters set up holding title,artist,genre etc
TrackFileManager.class is the serializer and where all the JsonObject.addProperty code is and returns a jsonObject back to MainActivity.
this jsonObject then gets added to an ArrayList called values as below in MainActivity.
(listOfFiles is another array list that holds all the filenames of my mp3s)
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Track.class, new TrackFileManager());
gsonBuilder.setPrettyPrinting();
//diasableHtmlEscaping stops & turning into \u0026
final Gson gson = gsonBuilder.disableHtmlEscaping().create();
values = new ArrayList<String>();
for (String file : listOfFiles){
t = new Track(path,file);
values.add(gson.toJson(t));
}
Collections.sort(values, new MyJSONComparator());
The other code i have is a MyJSONComparator.class which is below
public class MyJSONComparator implements Comparator<String> {
#Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
}
This produces no errors but it doesnt sort it which I know it wont but its the only way for it not hint errors in Android Studio. If I change the code to implements Comparator<Track> the Collections.sort produces an error. And unless I have comparator I can't access the elements i want to sort on like s1.getArtist etc unless thats not the right way to do it?? I've also tried ArrayList<String> and pretty much all other combinations of types except the one that works.
Im obviously doing something wrong, im pretty sure im almost there.
Can anyone help or at least point me in the right direction?
EDIT
public class MyJSONComparator implements Comparator<Track> {
#Override
public int compare(Track s1, Track s2) {
return s1.artist.compareTo(s2.artist);
}
}
The above code causes Collections.sort(values,new MyJSONComparator()); to error.
Sort (List<String>, java.util.Comparator<? super java.lang.string> in Collections cannot be applied to ArrayList<String>, com.example.MyJSONComparator.
Your compare function should take the objects as input
#Override
public int compare(Object s1, Object s2) {
return s1.artist.compareTo(s2.artist);
}
Collection.sort will work on the list ,having objects implementing the same comparator interface.
refer the below link
http://www.programcreek.com/2011/12/examples-to-demonstrate-comparable-vs-comparator-in-java/
What a difference a good night sleep makes.
Im not sure why I thought I needed to use Json's in the program. I obviously dont.
In the program Im creating a list of Tracks then converting them to json strings, then trying to sort the json on one of its elements. Why not just sort the tracks class on one of its elements instead.
Derp.
This now sits in the Track class and is triggered by Collections.sort(tracks). The instance of the track is built via another method using setters which is just like the .addproperty stuff I was doing in the Gson related code.
#Override
public int compareTo(Track t) {
return this.artist.compareTo(t.artist);
}
Code shown below works well when JSON object contains jsonKey as it was passed to the method. I wonder ... if there is a way to get a value assigned to a case insensitive representation of a key?
Example:
public String getOutputEventDescription(JsonElement outputEvent) throws ParserException {
return retrieveString(outputEvent, DESCRIPTION);
}
Should work regardless whether DESCRIPTION is defined as "Description", "description" or "DeScRipTIOn"
protected String retrieveString(JsonElement e, String jsonKey) throws ParserException {
JsonElement value = e.getAsJsonObject().get(jsonKey);
if (value == null) {
throw new ParserException("Key not found: " + jsonKey);
}
if (value.getAsString().trim().isEmpty()) {
throw new ParserException("Key is empty: " + jsonKey);
}
return e.getAsJsonObject().get(jsonKey).getAsString();
}
Unfortunately, registering a FieldNamingStrategy with the GsonBuilder wouldn't do much good, as it translates only in the opposite-than-desired direction: from the Java field name to the JSON element name. It cannot be reasonably used for your purposes.
(In Detail:
The result of the translation request ends at FieldNamingStrategy.translateName(Field), where the translated name is used to get the associated JSON element from a JsonObject, which has a LinkedHashMap<String, JsonElement>, called members, mapping JSON element names to their associated values. The translated name is used as the parameter to the get(String) method of members, and Gson provides no mechanism for this final call to be made case insensitive.
The members map is populated with calls to JsonObject.add(String, JsonElement), made from Streams.parseRecursive(JsonReader), with the JSON element name retrieved from the JsonReader used as the key to 'members'. (JsonReader uses the characters exactly as they are in the JSON, with the exception where the escape character '\' is found.) Throughout this call stack, Gson provides no mechanism for the keys used to populate members to be altered, e.g., to be made all lower case or all upper case.
A FieldNamingPolicy works in the same way.)
A reasonable solution might be to simply use a custom deserializer, along the following lines.
input.json:
[
{"field":"one"},
{"Field":"two"},
{"FIELD":"three"},
{"fIElD":"four"}
]
Foo.java:
import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.Map.Entry;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
public class Foo
{
public static void main(String[] args) throws Exception
{
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyClass.class, new MyTypeAdapter());
Gson gson = gsonBuilder.create();
MyClass[] myObjects = gson.fromJson(new FileReader("input.json"), MyClass[].class);
System.out.println(gson.toJson(myObjects));
}
}
class MyClass
{
String field;
}
class MyTypeAdapter implements JsonDeserializer<MyClass>
{
#Override
public MyClass deserialize(JsonElement json, Type myClassType, JsonDeserializationContext context)
throws JsonParseException
{
// json = {"field":"one"}
JsonObject originalJsonObject = json.getAsJsonObject();
JsonObject replacementJsonObject = new JsonObject();
for (Entry<String, JsonElement> elementEntry : originalJsonObject.entrySet())
{
String key = elementEntry.getKey();
JsonElement value = originalJsonObject.get(key);
key = key.toLowerCase();
replacementJsonObject.add(key, value);
}
return new Gson().fromJson(replacementJsonObject, MyClass.class);
}
}
Alternatively, you could first process the raw JSON to alter all of the element names to be the same case, all lower or all upper. Then, pass the altered JSON to Gson for deserialization. This would of course slow down JSON processing.
If you're able to change Gson code for your project, then probably the part to change for the most efficient result is the call to name = nextString((char) quote); in JsonReader. Since nextString(char) is also used to get the JSON element value, I'd probably just make a copy of it for getting the name, and then make small changes to force the element names to all lower or all upper case. Of course, this approach then locks your project to one release of Gson, else you'd need to repeat this change to upgrade to a newer Gson release.
With Jackson, the situation appears unfortunately similar. Translations with a PropertyNamingStrategy work in unfortunately much the same way: they translate from the Java field name to the JSON element name. None of the available JsonParser.Feature alterations would customize a JsonParser to force JSON element names to all upper or all lower case.
I faced the similar issue. I did this to get around the issue. (Replaced all the keys with their corresponding lowercase version and had all lower case fields in matching class). Hope this helps.
input = input.replaceAll("\\s","");
Matcher m = Pattern.compile("\"\\b\\w{1,}\\b\"\\s*:").matcher(input);
StringBuilder sanitizedJSON = new StringBuilder();
int last = 0;
while (m.find()) {
sanitizedJSON.append(input.substring(last, m.start()));
sanitizedJSON.append(m.group(0).toLowerCase());
last = m.end();
}
sanitizedJSON.append(input.substring(last));
input = sanitizedJSON.toString();
Unfortunately there doesn't seem to be a way in the current implementation to do this. If you look at the Gson source and more specifically at the JsonObject implementation you will see that the underlying data structure is a linked hash map. The get call simply invokes the get on the map, which in turn uses the hash code and equals method of your key to find the object you are looking for.
The only way around is to enforce some naming conventions for you keys. The easiest way would be to force all the keys to lowercase. If you need mixed case keys then you will have more difficulty and will need to write a more sophisticated algorithm for transforming the keys instead of simply calling jsonKey.toLowerCase().
I stumbled across this question when I ran into an issue where a different naming convention was being used at the two endpoints and subsequently discovered a less invasive solution.
Gson does support setting a naming convention that is used when mapping from the Java model names to the JSON names, both when when serializing and deserializing. Use the setFieldNamingPolicy method of the builder to change this behavior.
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
Gson gson = gsonBuilder.create();
See here for a nice article on the subject, including an overview of the different policies.
This isn't really a case insensitive solution, but it does provide a way to work around many of the situations where the case is not matching up.