In an API I'm working with I receive a certain part as a plain JSON array instead of a normal key => value pair. Its stupid, but that's what I have to deal with.
Example:
{"build":29625,"list": [
[312218505,1000,624437010,21997878697,35228,0,0,0],
[186873474,0,0,0,0,-1,0,0]
...
]}
The problem is getting Google GSON to map each array position to a field in a normal Object. Since unsurprisingly there isn't any native support for this I've had to roll my own solution: Each "request" that needs conversion implements a CustomConvert interface which has convertToObjects() and convertToJson() methods. Each "entry" has a bunch of fields with a #ArrayPosition(number) annotation. The convert methods translate between the two lists.
As this is ugly, is there a better way to do this with Google GSON or even another JSON library for Java?
Example from comments above, but this is the line of thinking I'm trying to get at. Note I haven't tested this or anything but at the very least it should put you on the path if you wanted to go this route.
class OuterObject {
String build;
List<InnerObject> objectList = new ArrayList<InnerObject>();
}
class InnerObject {
int field1, field2, field3, field4, field5, field6, field7, field8;
public InnerObject(int[] params) {
// assign params accordingly
}
}
class MyDeserializer implements JsonDeserializer<OuterObject> {
#Override
public OuterObject deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
OuterObject oo = new OuterObject();
JsonObject jo = je.getAsJsonObject();
oo.build = jo.get("build").getAsString();
JsonArray innerObjArrays = jo.getAsJsonArray("list");
for (JsonElement e : innerObjArrays)
{
JsonArray innerArray = e.getAsJsonArray();
// This is ugly but should give you the gist
InnerClass ic = new InnerClass((int[])jdc.deserialize(innerArray, int[].class));
oo.objectList.add(ic);
}
return oo;
}
}
Related
My code:
Controller
#RestController
public class CatalogController {
#Autowired
CatalogService catalogService;
#Autowired
Helper<List<?>> helperList;
#GetMapping("/addresses/{streetCode}")
public ResponseEntity<?> listByStreetCode(
#PathVariable("streetCode") String streetCode, #RequestParam Map<String, String> map) {
String showDoor = map.get("do");
return helperList.evaluateCorrectHttpResponse(
a -> catalogService.listAddressesByStreetCode(streetCode, showDoor ));
}
Service
#Service
public class CatalogService {
#Autowired
VAddressesRepository vAddressesRepository;
public List<VAddresses> listAddressesByStreetCode(String streetCode, String showDoor ) {
// The following repository output should contain every VAddresses attribute or every attribute but 'door', depending on the parameter 'showDoor '
return vAddressesRepository.findByStreetCode(Integer.valueOf(streetCode));
}
Entity
#Entity
#Table(name = "vAddress", schema = "foo")
public class VAddresses {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
String geoid;
String var1;
// other variables
String door;
// getters and setters
}
So, is there a way to skim model attributes based on showDoor value? Bare in mind that I have to operate on the service layer, not on the controller one because it is standardized.
I tried this example https://www.baeldung.com/jackson-serialize-field-custom-criteria. But I've gotten errors stating
"status": 500,
"error": "Internal Server Error",
"message": "Type definition error: [simple type, class foo.models.VAddresses]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot resolve PropertyFilter with id 'showDoorFilter'; no FilterProvider configured (through reference chain: java.util.ArrayList[0])",
even though I defined showDoorFilter in the service layer before invoking Addresses repository.
Should you have examples on how to apply baeldung's code in this case or better suggestions on how to deal with this issue, feel free to write them.
Thank you in advance for your cooperation.
First Method
I suggest that when you get the list of object from repository you can make a list of map, then for each object you can first convert it to a map then just remove the key corresponding to the object that need not be serialized from the corresponding map, it will done for all the object and return the list of map because as we know JsonFormat string is nothing but a combination of key and value pair so just by looking at json output It can't be easily differentiated whether it is an object or map in case like this.
Here you have to change the return type to List of Map
You can convert object to map using jackson ObjectMapper
below is the example
ObjectMapper objectMapper = new ObjectMapper();
UserName obj = new UserName();
obj.setName("mss");
obj.setSkills(Arrays.asList("java","spring"));
Map<String, Object> map = objectMapper.convertValue(obj, Map.class);
System.out.println(map);
Output
{name=mss, skills=[java, spring]}
Second Method
if you think this is too much of a work
then you can first convert the list that you got from repository to jsonArray and then for each jsonObject in the jsonArray remove the corresponding key which matches with the name of the field which you want to be serialized
Edit : sorry, I didn't know that you can only iterate with the for each loop on instances of java.lang.Iterable. JSONArray is not one of them.
so we will use normal variable for loop with the help of length of JSONArray
#Service
public class CatalogService {
#Autowired
VAddressesRepository vAddressesRepository;
public JSONArray listAddressesByStreetCode(String streetCode, String showDoor ) {
// The following repository output should contain every VAddresses attribute or every attribute but 'door', depending on the parameter 'showDoor '
List<VAddresses> lists = vAddressesRepository.findByStreetCode(Integer.valueOf(streetCode));
JSONArray jsonArray = new JSONArray(new ObjectMapper().writeValueAsString(lists));
int n = jsonArray.length();
for(int i = 0; i < n; ++i){
JSONObject jsonObject = (JSONObject)jsonArray.get(i);
jsonObject.remove(showDoor); // removing the key matching with the required field that is to be not serialized, key has to be in string format
jsonArray.put(i , jsonObject);
/* put the updated jsonObject in the index, even through Java is reference type so when you deleted the key
from jsonObject it should be updated in jsonArray index i, but for surety */
}
return jsonArray;
}
let me know it it works or not
I have an Abstract class with many concrete implementations:
public abstract Ticket {
private Long id;
private Currency fine;
...
}
public class SpeedingTicket extends Ticket {
public Currency getFine(){
// Expensive!
...
}
}
public class ParkingTicket extends Ticket {
public Currency getFine(){
// Eh, not so bad
...
}
}
When the concrete classes are serialized into JSON, it is wrapped with the classes simple name (speedingTickets or parkingTickets):
"_embedded": {
"speedingTickets" :
[{
"id":1,
"fine": "$190",
...,
},
{
"id":2,
"fine": "$100",
...,
}]
}
or
"_embedded": {
"parkingTickets" :[{
"id":100,
"fine": "$15",
...,
}]
}
Since I do not know, at runtime, which Ticket implementation I am receiving back, how can I parse the JSON out using the JSON Response API given the array is wrapped with the concrete implementations simple name?
I have a hack where I take the String value of the JSON and do String operations (substring, indexOf, etc) on it to return only what's in between the braces ("[...]"). I know there's a better way to do this...
After some research, I think I'll try the following tomorrow to see if it works:
JsonNode rootNode = mapper.readTree(jsonResponse);
String classImpl = Iterables.get(rootNode.get("_embedded").fields(), 0).textValue()
I can then say List<Ticket> tickets = response.readAsList(jsonResponse, "_embedded",classImpl) which should allow me to parse the JSON into a List
If you're using Jackson (as your tag suggests), you want to use Polymorphic Deserialization - which is exactly the problem of knowing how to deserialize to the correct subtype.
For example:
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="#class")
class { }
What this essentially does is include the class name in your JSON, so the deserializer has enough information to properly choose the subclass to instantiate. Something like this:
"_embedded": {
"parkingTickets" :[{
"_type": "ParkingTicket.class",
"id":100,
"fine": "$15",
...,
}]
}
You can just check the type by checking the variable the response contains.
JSONObject jsonObj = new JSONObject(response);
if(jsonObj.has("speedingTickets")){
// parse speedingTickets
}else if(jsonObj.has("parkingTickets")){
// parse parkingtickets
}
A JSON object is an unordered set of key/value pairs. A JSON array is an ordered collection of values. The values themselves could be objects or arrays.
In java it is easy to parse json with org.json library https://github.com/stleary/JSON-java
Short example how to parse json array:
String str = "{ \"number\": [3, 4, 5, 6] }";
JSONObject obj = new JSONObject(str);
JSONArray arr = obj.getJSONArray("number");
for (int i = 0; i < arr.length(); i++)
System.out.println(arr.getInt(i));
I am trying to parse the JSON from this link: https://api.guildwars2.com/v2/items/56 , everything fine until i met the line: "infix_upgrade":{"attributes":[{"attribute":"Power","modifier":4},{"attribute":"Precision","modifier":3}]} ...
If i dont get this wrong: infix_upgradehas 1 element attributes inside him. attributes has 2 elements with 2 other inside them. Is this a 2 dimension array?
I have tried (code too long to post):
JsonObject _detailsObject = _rootObject.get("details").getAsJsonObject();
JsonObject infix_upgradeObject = _detailsObject.get("infix_upgrade").getAsJsonObject();
JsonElement _infix_upgrade_attributesElement = infix_upgradeObject.get("attributes");
JsonArray _infix_upgrade_attributesJsonArray = _infix_upgrade_attributesElement.getAsJsonArray();
The problem is that I dont know what to do next, also tried to continue transforming JsonArray into string array like this:
Type _listType = new TypeToken<List<String>>() {}.getType();
List<String> _details_infusion_slotsStringArray = new Gson().fromJson(_infix_upgrade_attributesJsonArray, _listType);
but im getting java.lang.IllegalStateException: Expected STRING but was BEGIN_OBJECT which i guess comes from the attributes...
With a proper formatting (JSONLint, for example, checks if the JSON data is valid and does the formatting, which makes the structure more clear than what the GW link gives), attributes looks actually like this:
"attributes": [
{
"attribute": "Power",
"modifier": 4
},
{
"attribute": "Precision",
"modifier": 3
}
]
So it's an array of JsonObject and each object as two key-value pairs. This is why the parser throws an error because you require that this array contains only String which is not the case.
So the actual type is:
Type _listType = new TypeToken<List<JsonObject>>(){}.getType();
The problem is that I dont know what to do next
Hold on. You are using Gson and Java is an OO language so I suggest you to create classes.
This would be easier for you to fetch the datas afterward and for the parsing since you just need to provide the class of the actual class the JSON data represents to the parser (some edge-cases could be handled by writing a custom serializer/deserializer).
The data is also better typed than this bunch of JsonObject/JsonArray/etc.
This will give you a good starting point:
class Equipment {
private String name;
private String description;
...
#SerializedName("game_types")
private List<String> gameTypes;
...
private Details details;
...
}
class Details {
...
#SerializedName("infix_upgrade")
private InfixUpgrade infixUpgrade;
...
}
class InfixUpgrade {
private List<Attribute> attributes;
...
}
class Attribute {
private String attribute;
private int modifier;
...
}
and then just give the type to the parser:
Equipment equipment = new Gson().fromJson(jsonString, Equipment.class);
Hope it helps! :)
I have the following java code in my Android application and wanted a way to convert the Java list to an array that can be used in javascript:
Java:
public void onCompleted(List<GraphUser> users, Response response) {
for(int i = 0; i < users.size(); i++)
{
//add to an array object that can be used in Javascript
webView.loadUrl("javascript:fetchFriends(arrObj)");
}
}
Javascript:
//this is how I want to be able to use the object in Javascript
function parseFriends(usersObjectFromJava){
var users = [];
for (var i = 0; i < usersObjectFromJava.length; i++) {
var u = {
Id: usersObjectFromJava[i].id + "",
UserName: usersObjectFromJava[i].username,
FirstName: usersObjectFromJava[i].first_name,
LastName: usersObjectFromJava[i].last_name,
};
users[i] = u;
}
}
Could some help me with the Java code to create the usersObjectFromJava so that it can be used in javascript?
Use GSON
to convert java objects to JSON string, you can do it by
Gson gson = new Gson();
TestObject o1 = new TestObject("value1", 1);
TestObject o2 = new TestObject("value2", 2);
TestObject o3 = new TestObject("value3", 3);
List<TestObject> list = new ArrayList<TestObject>();
list.add(o1);
list.add(o2);
list.add(o3);
gson.toJson(list) will give you
[{"prop1":"value1","prop2":2},{"prop1":"value2","prop2":2},{"prop1":"value3","prop2":3}]
Now you can use JSON.parse(), to deserialize from JSON to Javascript Object.
I would assume doing this:
Java:
public void onCompleted(List<GraphUser> users, Response response) {
JSONArray arr = new JSONArray();
JSONObject tmp;
try {
for(int i = 0; i < users.size(); i++) {
tmp = new JSONObject();
tmp.put("Id",users.get(i).id); //some public getters inside GraphUser?
tmp.put("Username",users.get(i).username);
tmp.put("FirstName",users.get(i).first_name);
tmp.put("LastName",users.get(i).last_name);
arr.add(tmp);
}
webView.loadUrl("javascript:fetchFriends("+arr.toString()+")");
} catch(JSONException e){
//error handling
}
}
JavaScript:
function fetchFriends(usersObjectFromJava){
var users = usersObjectFromJava;
}
You will have to change the Java-Code a bit (i.e. using public getters or add more/less information to the JSONObjects.
JSON is included in Android by default, so no external libraries are necessary.
I hope i understood your problem.
Small thing i came across: you where using fetchFriends in Java but its called parseFriends in Javascript, I renamed them to fetchFriends
You can use Gson Library.
Gson gson = new GsonBuilder().create();
JsonArray jsonArray = gson.toJsonTree(your_list, TypeClass.class).getAsJsonArray();
http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/Gson.html
Use Jackson.
You'll need to add an " #JsonProperty" annotation to every property of your POJOs you want to pass, then do something like this:
String respStr = "";
for(Object whatever: MyList)
{
JSONObject dato = new JSONObject();
dato.put("FirstField", whatever.SomeData());
dato.put("SecondField", whatever.SomeData2());
StringEntity entity = new StringEntity(dato.toString());
post.setEntity(entity);
webView.loadUrl("javascript:fetchFriends("+entity+")");
}
I am not sure why no answer mentioned about jaxb. I am just thinking jaxb would be a good fit for this type of problems...
For a sample style of annotated jaxb class, please find this.
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ResponseAsList {
private List < Object > list = new ArrayList < Object > ();
public ResponseAsList() {
// private default constructor for JAXB
}
public List < Object > getList() {
return list;
}
public void setList(List < Object > list) {
this.list = list;
}
}
You will stuff your data in these lists and you will marshal either in xml or a json. After you get a json to the client, you can do a var myArray = JSON.parse(response);...
Although I typically advocate using something like GSON or Jackson to do JSON conversions for you, its pretty easy to roll your own if you're in a limited environment (like Android) and don't want to bundle a bunch of dependencies.
public class JsonHelper {
public static String convertToJSON(List<GraphUser> users) {
StringBuilder sb = new StringBuilder();
for (GraphUser user : users) {
sb.append(convertToJSON(user));
}
return sb.toString();
}
public static String convertToJSON(GraphUser user) {
return new StringBuilder()
.append("{")
.append("\"id\":").append(user.getId()).append(",")
.append("\"admin\":").append(user.isAdmin() ? "true" : "false").append(",")
.append("\"name\":\"").append(user.getName()).append("\",")
.append("\"email\":\"").append(user.getEmail()).append("\"")
.append("}")
.toString();
}
}
You could obviously make a toJSON() method on GraphUser to put the logic if you prefer. Or use an injectable json helper library instead of static methods (I would). Or any number of other abstractions. Many developers prefer to separate representation of model objects into their own object, myself included. Personally, I might model it something like this if I wanted to avoid dependencies:
interface Marshaller<F,T> with methods T marshall(F obj) and F unmarshall(T obj)
interface JsonMarshaller<F> extends Marshaller<String>
class GraphUserMarshaller implements JsonMarshaller<GraphUser>
class GraphUserCollectionMarshaller implements JsonMarshaller<Collection<GraphUser>> which could do type-checking or use the visitor pattern or something to determine the best way to represent this type of collection of objects.
Along the way, I'm sure you'll find some repeated code to extract to super- or composite- classes, particularly once you start modeling collection marshallers this way. Although this can get to be pretty verbose (and tedious), it works particularly well in resource-constrained environments where you want to limit the number of libraries on which you depend.
You can use the Google Gson library (http://code.google.com/p/google-gson/) to convert the Java List Object to JSON. Ensure that the right fields are set like ID, UserName, FirstName, etc and on the java script side that same code would work.
Its just an example, First add javascript interface. It will be a bridge between javascript and java code.
webView.addJavascriptInterface(new JSInterface(), "interface");
In javascript you can add like this,
"window.interface.setPageCount(pageCount1);"
The interface is a keyword in common between java and javascript. create a class JSInterace and define a method setPageCount(int a). The script will return a value, and you can use that value in your java method
My sample JSON input is as follows:
"JobName":"Test Job 1",
"events":[
{ "features":[],
"InputHiveTable":"uilog_uiclientlogdata",
"eventColumn":"command",
"name":"edu.apollogrp.classroom.discussion.client.events.CreateDiscussionEvent"
},
Consider the field "InputHiveTable", it could be in all uppercase INPUTHIVETABLE, all lowercase inputhivetable, or a mixture of both as it is now.
Currently, I'm reading the field as follows (in Java):
JSONObject jsonObject = (JSONObject) obj;
JSONArray events = (JSONArray) jsonObject.get("events");
String InputHiveTable = (String)event.get("InputHiveTable");
So my question is how do I search for the field "InputHiveTable" while ignoring the case.
I'm using JSON Simple libraries.
If you have to perform this case-insensitive lookup many times, I'd just write a method to do that lookup:
public Object getIgnoreCase(JSONObject jobj, String key) {
Iterator<String> iter = jobj.keySet().iterator();
while (iter.hasNext()) {
String key1 = iter.next();
if (key1.equalsIgnoreCase(key)) {
return jobj.get(key1);
}
}
return null;
}
You could read the JSONObject into a java string, and call String.toLowerCase on it and store it back into a JSONObject. This will turn the entire case of the string to lower case, so you will have to account for that elsewhere in your logic. After that, you then would just have to do a get call on "inputhivetable".
By no means is it a pretty solution, but it is a potential work around if there is absolutely no other way for you to handle what you're returning as your JSON input.
Given that case-insensitivity can be achieved with TreeMap (i.e. via String.CASE_INSENSITIVE_ORDER comparator), you can probably do the following:
Implement your own MyJSONObject extending TreeMap where its methods will be just calling static methods of JSONObject with the same signatures and all required interfaces as in JSONObject. In default constructor write super(String.CASE_INSENSITIVE_ORDER)
Implement ContainerFactory interface where createObjectContainer will return new instance of MyJSONObject (and createArrayContainer will just return new JSONArray).
To run it with new container MyContainerFactory:
StringReader in = new StringReader(yourJSONString);
JSONParser parser = new JSONParser();
parser.parse(in, yourContainerFactory)