I want to write a generic function to parse a json string (stringEtcdContent below in the code). The string has a list of objects with the key "value". I parse the json into a tree, get a list of JsonNode's (valueNodes below) with the string to parse using a generic class. The class in which I have the function is this: "public abstract class DashboardReportProvider". Based on a similar question here, I wrote this function:
#SuppressWarnings("unchecked")
public List<T> getStatusList(String path) {
Class<T> clazz;
clazz = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
List<T> statusList = new ArrayList<>();
T statusItem;
ObjectMapper mapper = new ObjectMapper();
try {
String stringEtcdContent = etcdCommandExecutor.getEtcdValue(path);
JsonParser parser=new MappingJsonFactory().createParser(stringEtcdContent);
JsonNode rootNode=parser.readValueAsTree();
List<JsonNode> valueNodes=rootNode.findValues("value");
Iterator<JsonNode> valueNodesIterator=valueNodes.listIterator();
while (valueNodesIterator.hasNext()) {
JsonNode valueNode=(JsonNode)valueNodesIterator.next();
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
String valueString = writer.writeValueAsString(valueNode);
statusItem = mapper.readValue(valueString, clazz);
statusList.add(statusItem);
}
return statusList;
} catch (Exception e) {
LOG.error(e.getMessage());
}
return statusList;
}
It compiles fine, but when I try to run the code, I get this error:
"[ERROR] java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType". What is wrong?
Try ObjectMapper ,
new ObjectMapper().readValue(path,Object.class);
where Object.class - cast to type
Related
Can I somehow alter ObjectMapper to be able to handle null and empty values?
Let's say that my value is read as
objectMapper.readValue(val, new TypeReference<Object>() {});
Where val is
val = new ByteArrayInputStream(new byte[] {});
I don't have control over value that is passed and I cannot check for buffer length prior to executing readValue.
I've tried configuring mapper with DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT such as:
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
But I still get the com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input error. Is it possible to somehow have Jackson ignore empty values and just return null?
Is it possible to somehow have Jackson ignore empty values and just return null?
You can successfully dial with an empty byte array, or an empty input stream, by using a more low-level streaming API.
That's the core idea of how you can ensure that there's some to parse by employing a JsonParser before feeding the data into an ObjectMapper:
byte[] jsonBytes1 = {};
ObjectMapper mapper = new ObjectMapper();
JsonParser parser = mapper.getFactory().createParser(jsonBytes1);
JsonNode node = parser.readValueAsTree();
MyPojo myPojo = null;
if (node != null) {
myPojo = mapper.treeToValue(node, MyPojo.class);
}
So we're parsing the input into a JsonNode and checking it manually, only if it's not null ObjectMapper comes into play.
If we extract this logic into a separate method, that's it might look like (Java 8 Optional might be handy in this case a return type):
public static <T> Optional<T> convertBytes(byte[] arr,
Class<T> pojoClass,
ObjectMapper mapper) throws IOException {
JsonParser parser = mapper.getFactory().createParser(arr);
JsonNode node = parser.readValueAsTree();
return node != null ? Optional.of(mapper.treeToValue(node, pojoClass)) : Optional.empty();
}
Usage example
Consider a simple POJO:
public class MyPojo {
private String name;
// getter, setters and toString
}
main()
public static void main(String[] args) throws IOException {
String source = """
{
"name" : "Alice"
}
""";
byte[] jsonBytes1 = {};
byte[] jsonBytes2 = source.getBytes(StandardCharsets.UTF_8);
ObjectMapper mapper = new ObjectMapper();
System.out.println(convertBytes(jsonBytes1, MyPojo.class, mapper));
System.out.println(convertBytes(jsonBytes2, MyPojo.class, mapper));
}
Output:
Optional.empty
Optional[Test.MyPojo(name=Alice)]
Below my function,
public <T> List<T> jsonToListBean(String json, Class<T> clazz) {
Gson gson = new Gson();
Type listType = new TypeToken<List<T>>(){}.getType();
List<T> returnValue = gson.fromJson(json, listType);
return returnValue;
}
but the system returns this exception:
java.lang.ClassCastException: com.google.gson.internal.StringMap
I notice also that if I use the following line code
Type listType = new TypeToken<ArrayList<ShapeBean>>(){}.getType();
it works, but my target is to be absolutly as generic as possible to make my code more efficient.
How should i change my code to make usable also passing the type "T" instead the "ShapeBean"?
Solved. Thanks to kan's link below the code:
public <T> List<T> listEntity(String jsonCommand, Class<T> clazz) {
List<T> lst = new ArrayList<T>();
try {
// Consuming remote method
JsonParser parser = new JsonParser();
JsonArray array = parser.parse(jsonCommand).getAsJsonArray();
for(final JsonElement json: array){
Gson g = new Gson();
T entity = g.fromJson(json, clazz);
lst.add(entity);
}
} catch (Exception e) {
e.printStackTrace();
} return lst;
}
check also this:
Gson TypeToken with dynamic ArrayList item type
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();
}
}
When I have a list inside a generic method Gson returns a list of Object instead a list of generic type.
I've seen a lot of threads with no solution, and if I dont use a generic method would have to create a method for each bean.
Is there any one with any idea what do I have to do so solve it?
PS: For a while I've created loop into the list to serialize entity by entity, splitting returned String and deserializing entity by entity, but clearly it's a workaround
Creating a generic list and serializing to JSON (this is a webservices method):
public String listEntity(String nomeClasse) throws WsException {
// Identifying the entity class
Class<?> clazz = Class.forName(nomeClasse);
// Querying with Hibernate
List<?> lst = getDao().listEntity(clazz);
// Check if is null
if (lst == null) {
return "[]";
}
return gson.toJson(lst);
}
Consuming the Webservice method:
public <T> List<T> listEntity(Class<T> clazz)
throws WsIntegracaoException {
try {
// Consuming remote method
String strJson = getService().listEntity(clazz.getName());
Type type = new TypeToken<List<T>>() {}.getType();
// HERE IS THE PROBLEM
List<T> lst = GSON.fromJson(strJson, type);
// RETURNS IS A LIST OF OBJECT INSTEAD OF A LIST OF <T>
return lst;
} catch (Exception e) {
throw new WsIntegracaoException(
"WS method error [listEntity()]", e);
}
}
Invoking the generic method:
List<City> list = listEntity(City.class);
// Here I get a ClassCastException
fillTable(list);
List Element (wrong):
java.lang.Object#23f6b8
Exception:
java.lang.ClassCastException: java.lang.Object cannot be cast to java.io.Serializable
SOLUTION - THIS WORKED FOR ME: Gson TypeToken with dynamic ArrayList item type
public <T> List<T> listEntity(Class<T> clazz)
throws WsIntegracaoException {
try {
// Consuming remote method
String strJson = getService().listEntity(clazz.getName());
JsonParser parser = new JsonParser();
JsonArray array = parser.parse(strJson).getAsJsonArray();
List<T> lst = new ArrayList<T>();
for(final JsonElement json: array){
T entity = GSON.fromJson(json, clazz);
lst.add(entity);
}
return lst;
} catch (Exception e) {
throw new WsIntegracaoException(
"WS method error [listEntity()]", e);
}
}
My question is very similiar to this:
Unable to parse Json array using Gson
But I cann't get the answer from it.
The answer from above link:
public static List<MapData> getData(){
Gson gson = new Gson();
String jsonString = "[{\"id\":18,\"city\":\"test\",\"street\":\"test 1\",\"zipcode\":121209,\"state\":\"IL\",\"lat\":32.158138,\"lng\":34.807838},{\"id\":19,\"city\":\"test\",\"street\":\"1\",\"zipcode\":76812,\"state\":\"IL\",\"lat\":32.161041,\"lng\":34.810410}]";
Type type = new TypeToken<List<MapData>>(){}.getType();
return gson.fromJson(jsonString, type);
}
It works well, but I want to use implicit operator on generic type. See below:
public static <T> List<T> getData(Class<T> classT){
Gson gson = new Gson();
String jsonString = "[{\"id\":18,\"city\":\"test\",\"street\":\"test 1\",\"zipcode\":121209,\"state\":\"IL\",\"lat\":32.158138,\"lng\":34.807838},{\"id\":19,\"city\":\"test\",\"street\":\"1\",\"zipcode\":76812,\"state\":\"IL\",\"lat\":32.161041,\"lng\":34.810410}]";
Type type = new TypeToken<List<T>>(){}.getType();
return gson.fromJson(jsonString, type);
}
And then I try to pass the Class argument to the method:
List<MapData> data = getData(MapData.class);
System.out.println(data.get(0).city);
Then an error was arised:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.ssc.ctq.nav.util.MapData
Can anyone tell me why I get this error? Is implicit operator is not supported in TypeToken class?
you can do like this:
Gson gson = new Gson();
String jsonString = "[{\"id\":18,\"city\":\"test\",\"street\":\"test 1\",\"zipcode\":121209,\"state\":\"IL\",\"lat\":32.158138,\"lng\":34.807838},{\"id\":19,\"city\":\"test\",\"street\":\"1\",\"zipcode\":76812,\"state\":\"IL\",\"lat\":32.161041,\"lng\":34.810410}]";
List<Map> tmpList = gson.fromJson(jsonString);
List<T> resultList = new Arraylist<T>(tmplist.size());
for(Map map:tmpList){
String tmpJson = gson.toJson(map);
resultList.add(gson.fromJson(tmpJson, classT));
}
return resultList;
I met the same problem. From the Javadoc of TypeToken:
This syntax cannot be used to create type literals that have wildcard parameters, such as Class<?> or List<? extends CharSequence>.
You must explicitly indicate the type of T in TypeToken<T>, without generics.
You can use this method in order to parse generic json string to map
public Map<String, String> getMapFromJson(String jsonString) {
Map<String, String> map = new HashMap<>();
try {
JSONObject object = new JSONObject(jsonString);
Iterator<?> iterator = object.keys();
while (iterator.hasNext()) {
String key = (String) iterator.next();
if(!key.isEmpty() && !object.getString(key).isEmpty()){
map.put(key, object.getString(key));
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return map;
}