I am consuming Thirdparty jsonString, I am trying to parse the json but sometimes JSON object "RadarReports" is an list and sometimes it object.
{"RadarReports": {
"executionTime": "135",
"RadarReport": {
"abc": "1116591",
"name": "abc",
"id": "2019050311582056119",
"ownerId": "xyz"
},
"size" :"1"
}}
=================
{"RadarReports": {
"executionTime": "113",
"RadarReport": [
{
"abc": "1116591",
"name": "abc",
"id": "2019050311582056119",
"ownerId": "xyz"
},
{
"abc": "1116591",
"name": "abc",
"id": "2019050311582056119",
"ownerId": "xyz"
},
]
"size" : "2"
}}
I tried below to parse but failing when single object came into picture, need to accept both single and list of objects.
#Data
public class Radarreports {
private int size;
private ArrayList<RadarreportSet> RadarReportSet;
private ArrayList<RadarReport> RadarReport;
}
#Data
public
class ReportsResponse {
Radarreports RadarReports;
}
URL url = new URL(queryUrl);
BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
Gson gson = new GsonBuilder().create();
ReportsResponse radarReports = gson.fromJson(br, ReportsResponse.class);
You could solve this with a custom TypeAdapterFactory which creates an adapter which first peeks at the type of the JSON data and then adds special handling where the JSON object is not wrapped in an JSON array:
// Only intended for usage with #JsonAdapter
class SingleObjectOrListAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Note: Cannot use getDelegateAdapter due to https://github.com/google/gson/issues/1028
TypeAdapter<T> listAdapterDelegate = gson.getAdapter(type);
TypeAdapter<JsonObject> jsonObjectAdapter = gson.getAdapter(JsonObject.class);
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) throws IOException {
listAdapterDelegate.write(out, value);
}
#Override
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.BEGIN_OBJECT) {
// Wrap JSON object in a new JSON array before parsing it
JsonObject jsonObject = jsonObjectAdapter.read(in);
JsonArray jsonArray = new JsonArray();
jsonArray.add(jsonObject);
return listAdapterDelegate.fromJsonTree(jsonArray);
} else {
return listAdapterDelegate.read(in);
}
}
};
}
}
The factory can then be specified for the affected field with #JsonAdapter:
#JsonAdapter(SingleObjectOrListAdapterFactory.class)
private ArrayList<RadarReport> RadarReport;
Related
I am writing a REST client using RestTemplate and GSON.
Below is sample of my JSON response
{
"value": [
{
"properties": {
"vmId": "f7f953fb-d853-4373-b564-",
"hardwareProfile": {
"vmSize": "Standard_D2"
},
},
"name": "A",
"Id": ""
},
{
"properties": {
"vmId": "f7f953fb-d853-4373-b564-",
"hardwareProfile": {
"vmSize": "Standard_D2"
},
},
"name": "B",
"Id": ""
},
{
"properties": {
"vmId": "f7f953fb-d853-4373-b564-",
"hardwareProfile": {
"vmSize": "Standard_D2"
},
},
"name": "C",
"Id": ""
}
]
}
What I want to is that I want to get only the values for the property --> "name"
So I created a simple POJO that has only name as the member field.
public class VMNames {
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and I am trying to use the GSON like this to get a array of this POJO. Here, the response is my JSON response object.
Gson gson = new Gson();
VMNames[] vmNamesArray = gson.fromJson(response.getBody(), VMNames[].class);
System.out.println(vmNamesArray.length);
But when I do this, I get an error i.e. as below:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
Please note that I don't want to create a POJO that has exactly same structure as my JSON object because I want to get only one attribute out of my JSON object. I am hoping that I won't have to really create a POJO with the same structure as my JSON response because, in reality, it's a huge response and I don't control it, so it can also change tomorrow.
Can you try this:
public class VMNames {
#SerializedName("name")
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Type collectionType = new TypeToken<Collection<VMNames>>(){}.getType();
Collection<VMNames> vmNamesArray = gson.fromJson(response.getBody(), collectionType);
System.out.println(vmNamesArray.length);
or try:
VMNames[] vmNamesArray = gson.fromJson(response.getBody(), VMNames[].class);
So I could get this done. Posting the answer to this so that someone can be benefited tomorrow :)
First thing, i stopped using GSON and started using JSON.
And below is the code that helped.
RestTemplate restTemplate = new RestTemplate();
response = (ResponseEntity<String>) restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
JSONObject jsonObj = new JSONObject(response.getBody().toString());
JSONArray c = jsonObj.getJSONArray("value");
for (int i = 0; i < c.length(); i++) {
JSONObject obj = c.getJSONObject(i);
String VMName = obj.getString("name");
VMNames vmnames = new VMNames();
vmnames.setName(VMName);
vmNames.add(vmnames);
}
return vmNames;
And i get a list of all the value against the attribute name in form of a json array.
I have a JSON with list of Objects and any of the item in the list can have null or the same object as a value for a key. I am looking for a faster way to parse the json to arrive at my final result.
The data structure looks like -
[
{
"firstName": "Bruce",
"familyMembers": null
},
{
"firstName": "Gates Family",
"familyMembers": [
{
"firstName": "Bill",
"familyMembers": null
},
{
"firstName": "Steve",
"familyMembers": null
}
]
},
{
"firstName": "Lee",
"familyMembers": null
},
{
"firstName": "Chan",
"familyMembers": null
}
]
The output should be a set = ("Bruce", "Bill", "Steve", "Lee", "Chan").
I am looking for a best possible way to do this in Java, such that i dont increase my response time by getting caught in this parsing hell.
Appreciate your time on this.
Try my recursive implementation.
public static void jsonArrayToSet(JSONArray jAry, Set<String> result, String targetKey, String subArrayKey, boolean includeNode){
try {
for (int i = 0; i < jAry.length(); i++) {
JSONObject jObj = jAry.getJSONObject(i);
boolean hasSubArray = false;
JSONArray subArray = null;
if(jObj.has(subArrayKey)){
Object possibleSubArray = jObj.get(subArrayKey);
if(possibleSubArray instanceof JSONArray){
hasSubArray = true;
subArray = (JSONArray) possibleSubArray;
}
}
if(hasSubArray){
if(includeNode){
result.add(jObj.getString(targetKey));
}
jsonArrayToSet(subArray, result, targetKey, subArrayKey, includeNode);
} else {
result.add(jObj.getString(targetKey));
}
}
} catch (JSONException e){
e.printStackTrace();
}
}
jAry: The source JSONArray.
result: The Set you want to write in.
targetKey: The key that maps to an entry which you want to add to result.
subArrayKey: The key that map to a sub-JSONArray.
includeNode: When current JSONOnject is a node containing sub-array, add it to result or not.
In your case, you can call:
jsonArrayToSet(yourJsonArray, yourSet, "firstName", "familyMembers", false);
As mentioned in my comment.
Your first issue would be the content in your JSON file. Based on the standard, it should be wrapped around with a set of { }.
Example
{
"members": [
{
"firstName": "Bruce",
"familyMembers": null
},
{
"firstName": "Gates Family",
"familyMembers": [
{
"firstName": "Bill",
"familyMembers": null
},
{
"firstName": "Steve",
"familyMembers": null
}
]
},
{
"firstName": "Lee",
"familyMembers": null
},
{
"firstName": "Chan",
"familyMembers": null
}
]
}
Also, I think the value "Gates Family" should be part of the output? Since it is under the "FirstName" attribute.
Anyway, here is my solution that is based on the org.json library. It also uses Goggle's GSon library where I use it for reading the JSON file.
import org.json.*;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
public class solution {
public static final String JSON_DATA_FILE_PATH = "./data/source_37848106.json";
private static boolean hasMoreFamilyName(JSONObject json) {
return json.has("familyMembers") && json.get("familyMembers") != JSONObject.NULL;
}
private static void trackFirstName(Map<String, Integer> nameTracker, JSONObject json) {
if (!nameTracker.containsKey(json.getString("firstName"))) {
nameTracker.put(json.getString("firstName"), /*DUMMY VALUE =*/1);
}
}
private static void getNames(Map<String,Integer> nameTracker, JSONArray jsonArr) {
for (int i = 0; i< jsonArr.length(); i++) {
JSONObject item = jsonArr.getJSONObject(i);
if (hasMoreFamilyName(item)) {
getNames(nameTracker, item.getJSONArray("familyMembers"));
}
trackFirstName(nameTracker, item);
}
}
public static void main(String[] args) {
Map<String, Integer> nameTracker = new HashMap<>();
try {
String text = Files.toString(new File(JSON_DATA_FILE_PATH), Charsets.UTF_8);
JSONObject json = new JSONObject(text);
getNames(nameTracker, json.getJSONArray("members"));
}
catch (Exception ex) {
System.out.println("Something is wrong.");
}
for (Map.Entry<String,Integer> entry : nameTracker.entrySet()) {
System.out.println(entry.getKey());
}
}
You can use ObjectMapper defined in jackson-databind-2.6.5.jar
Define a java class with fields similar to json pattern and then just bind them like:
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
Family family=objectMapper.readValue(jsonString, Family.class);
Now you will have a java object similar to your json string pattern and you can print it the way you like.
I am working with an API that responds like the following for a single user resource:
{
"data": {
"id": 11,
"first_name": "First",
"last_name": "Last",
"books": {
"data": [
{
"id": 13,
"name": "Halo"
}
]
},
"games": {
"data": [
{
"id": 1,
"name": "Halo"
}
]
}
}
}
or like the following for multiple user resources:
{
"data": [
{
"id": 11,
"first_name": "First",
"last_name": "Last",
"books": {
"data": [
{
"id": 13,
"name": "Halo"
}
]
},
"games": {
"data": [
{
"id": 1,
"name": "Halo"
}
]
}
},
],
"meta": {
"pagination": {
"total": 11,
"count": 10,
"per_page": 10,
"current_page": 1,
"total_pages": 2,
"links": {
"next": "http://api.###.com/users?page=2"
}
}
}
}
Key things to notice are:
all resources are nested under a data key, single as an object or multiple as an array of objects. This includes nested resources such as books and games in the example above.
I need to be able retrieve the values of the meta key for my pagination routines
User model
public class User extends BaseModel {
public Integer id;
public String firstName;
public String lastName;
public List<Book> books; // These will not receive the deserialized
public List<Game> games; // JSON due to the parent data key
}
Custom JSON deserializer
public class ItemTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
// If the data key exists and is an object or array, unwrap it and return its contents
if (jsonObject.has("data") && (jsonObject.get("data").isJsonObject() || jsonObject.get("data").isJsonArray())) {
jsonElement = jsonObject.get("data");
}
}
return delegate.fromJsonTree(jsonElement);
}
}.nullSafe();
}
}
This is all working fine but I can't figure out how to access the meta key for pagination.
Ideally I would get Gson to deserialize the response to the following POJO:
public class ApiResponse {
public Object data;
public Meta meta
}
and I could just cast the response field to the correct type in the response callback like the following:
Map<String, String> params = new HashMap<String, String>();
params.put("include", "books,games");
ApiClient.getClient().authenticatedUser(params, new ApiClientCallback<ApiResponse>() {
#Override
public void failure(RestError restError) {
Log.d("TAG", restError.message);
}
#Override
public void success(ApiResponse response, Response rawResponse) {
User user = (User) response.data; // Cast data field to User type
Log.d("TAG", user.firstName);
Log.d("TAG", "Total pages" + response.meta.pagination.total.toString()); // Still have access to meta key data
}
});
However the data field of the ApiResponse object is null.
My Java is very rusty and I have no idea if this is even possible nor do I understand how to go about it correctly, any help would be much appreciated.
I needed the same thing and have managed to get it working by adding an if statement to your custom serializer:
…
// If the meta key exists, consider the element to be root and don't unwrap it
if (!jsonObject.has("meta")) {
// If the data key exists and is an object or array, unwrap it and return its contents
if (jsonObject.has("data") && (jsonObject.get("data").isJsonObject() || jsonObject.get("data").isJsonArray())) {
jsonElement = jsonObject.get("data");
}
}
…
The reason why the data field of your ApiResponse was null is because your original deserializer was processing the whole response and making you "loose" the root object's data and meta elements.
I've also parametized the ApiResponse class:
public class ApiResponse<T> {
public Meta meta;
public T data;
}
That way deserializing still works without creating many different Response classes, while casting isn't needed anymore and you can specify the type of ApiResponse's data field as needed (eg. ApiResponse<User> for single user resource, ApiResponse<List<User>> for multiple user resources, etc.).
How to create javabean for gson for the below JSON script?
{
"header": [
{
"title": {
"attempts": 3,
"required": true
}
},
{
"on": {
"next": "abcd",
"event": "continue"
}
},
{
"on": {
"next": "",
"event": "break"
}
}
]
}
I'm trying to build the javabean for this JSON output. I'm not able to repeat the fieldname on.
Please suggest any solutions.
You will need multiple classes to accomplish this. I made some assumptions with the naming, but these should suffice:
public class Response {
private List<Entry> header;
private class Entry {
private Title title;
private On on;
}
private class Title {
int attempts;
boolean required;
}
private class On {
String next, event;
}
}
You can test it with a main() method like:
public static void main(String[] args) {
// The JSON from your post
String json = "{\"header\":[{\"title\":{\"attempts\":3,\"required\":true}},{\"on\":{\"next\":\"abcd\",\"event\":\"continue\"}},{\"on\":{\"next\":\"\",\"event\":\"break\"}}]}";
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Response response = gson.fromJson(json, Response.class);
System.out.println(response.header.get(0).title.attempts); // 3
System.out.println(response.header.get(1).on.next); // abcd
System.out.println(gson.toJson(response)); // Produces the exact same JSON as the original
}
I have String object with template base, something like:
<h1>{{header}}</h1>
{{#bug}}
{{/bug}}
{{#items}}
{{#first}}
<li><strong>{{name}}</strong></li>
{{/first}}
{{#link}}
<li>{{name}}</li>
{{/link}}
{{/items}}
{{#empty}}
<p>The list is empty.</p>
{{/empty}}
I want to pull another String object representing JSONObject and put its fields into template:
{
"header": "Colors",
"items": [
{"name": "red", "first": true, "url": "#Red"},
{"name": "green", "link": true, "url": "#Green"},
{"name": "blue", "link": true, "url": "#Blue"}
],
"empty": false
}
In the end I would get String representing HTML structure:
<h1>Colors</h1>
<li><strong>red</strong></li>
<li>green</li>
<li>blue</li>
I don't want to use any POJOs or Maps - only use standard String objects or alternatively convert second String into JSONObject to use it as a template's context.
Could someone give me any example how to achieve that?
Thanks.
Edit: I don't know anything about template/JSON structure while executing template - I have to play with unknown template/JSON and assume that they are correct.
I couldn't find way to work on pure String objects - I am converting JSONObject to Map to get it working with Mustache. This is code for conversion:
public static Map<String, Object> toMap(JSONObject object) throws JSONException
{
Map<String, Object> map = new HashMap();
Iterator keys = object.keys();
while (keys.hasNext())
{
String key = (String) keys.next();
map.put(key, fromJson(object.get(key)));
}
return map;
}
public static List toList(JSONArray array) throws JSONException
{
List list = new ArrayList();
for (int i = 0; i < array.length(); i++)
{
list.add(fromJson(array.get(i)));
}
return list;
}
private static Object fromJson(Object json) throws JSONException
{
if (json instanceof JSONObject)
{
return toMap((JSONObject) json);
} else if (json instanceof JSONArray)
{
return toList((JSONArray) json);
} else
{
return json;
}
}
Usage:
mustacheTemplate.execute(JSONUtils.toMap(new JSONObject(myString)));