I would like to read in the string {"a": 1.0} as a generic Java Object while keeping the same string format. However, when I try, Jackson automatically changes the internal representation to {a = 1}. In other words, how can I get the following code to print {"a": 1.0} instead of {a = 1}? Note that, I have to read it in as an Object (due to other program constraints).
import org.codehaus.jackson.map.ObjectMapper;
public class Main {
public static void main(String[] args) {
try
{
ObjectMapper mapper = new ObjectMapper();
Object myObject = mapper.readValue("{\"a\": 1.0}", Object.class);
System.out.println(myObject.toString());
}
catch (Exception e)
{
e.printStackTrace();
System.err.println(e.getMessage());
}
}
}
The created object will be a map (like the other comments) and so its toString produces what you're seeing, {a = 1}. To get your code to print something closer to your input value, you need to use Jackson to write it back out with something like:
System.out.println(mapper.writeValueAsString(myObject));
That gives me what I believe you're looking for:
{"a":1.0}
In other words, Jackson has deserialized your input string into an arbitrary Java object. When you call toString on the object, its own toString is, of course, used. This can write the object however it pleases, including using the method from Object. To reproduce the input string, you have to use Jackson to serialize our object back out.
You need an existing class that matches the desired json structure. Object is not such class. You can still refer to it as Object, if that's needed:
Object myObject = mapper.readValue("{\"a\": 1.0}", SomeClass.class);
If you use a debugger, you will see that the type of the returned Object is LinkedHashMap. So what you see is the output of LinkedHashMap.toString(). There's no way for Jackson to change that, so you can either cast it to a Map and create the String yourself or ask for another return type that generates the JSON String for you:
if(myObject instanceof Map<?, ?>){
final Map<?, ?> map = (Map<?, ?>) myObject;
final StringBuilder sb = new StringBuilder("{");
boolean first = true;
for(final Entry<?, ?> entry : map.entrySet()){
if(first){
first = false;
} else{
sb.append(",");
}
sb.append("\n\t'")
.append(entry.getKey())
.append("':'")
.append(entry.getValue())
.append("'");
}
if(!first){
sb.append("\n");
}
sb.append("}");
System.out.println(sb.toString());
} else{
System.out.println(myObject);
}
Output:
{
'a':'1.0'
}
When Jackson is told to bind JSON into Object.class, it does just that; but since it has no a priori knowledge of what might be in that JSON (or what classes one might want to use), it has to use most basic Java types: Maps, Lists, Numbers, Booleans and Strings. So any JSON Object is represented by Map; JSON Array by List, and so on.
If you want a custom object, you must specify its type; or, when serializing, enable inclusion of explicit type information ("polymorphic type handling"). This will add either class name, or type name, and can be used to deserialize back to exact type.
To do this, either type itself (or one of its supertypes) must use #JsonTypeInfo annotation; or, if it is an Object property, #JsonTypeInfo for property (field or method).
Related
I need to parse the same json stream twice, one time to identify say the length of array in the json stream, and next to parse the entities. However, there is only a single instance of JsonParser to start with. Is there a way I can clone this or create a copy of this because once the instance is used to parse, it can't be reused for re-parsing the same json stream obviously.
Thanks in advance.
Example:
static class ResultEntitiesContainer {
List<ResultEntity> resultEntities;
// getter and setters available
}
void parseEntities(JsonParser parser) {
// Need to extract number of entities.
int count=0;
ObjectMapper om = new ObjectMapper();
JsonNode node = om.readTree(parser);
node = node.get("resultEntities");
if (node.isArray()) {
count = node.size();
}
// Need to parse the entities in the json node
ResultEntitiesContainer rec = om.readValue(parser, ResultEntitiesContainer.class);
}
This answer aims to address the question of cloning the JsonParser assuming it is required.
com.fasterxml.jackson.core.JsonParser is a public abstract class and it does not provide a clone or similar method.
An abstract class may be extended by different implementations that the author of JsonParser.java has no control of.
Similarly it is not safe to clone a JsonParser as an argument of void parseEntities(JsonParser parser); because the author of parseEntities cannot be sure which implementation is used and whether it can be cloned.
However if you (as the author of parseEntities) do have control over the used implementations, then it is safe to clone the known implementations (assuming this is possible).
So if you do know which specific implementation (or implementations) of JsonParser your class will be using, you can try and clone specifically these known implementations.
E.g. add and implemented one or more methods (as needed) like:
void parseEntities(MyJsonParser parser);
void parseEntities(MyOtherJsonParser parser);
Then it is a question of cloning the specific implementations of JsonParser that are used. For instance assuming MyJsonParser supports cloning the following could be valid.
void parseEntities(MyJsonParser parser){
MyJsonParser clonedParser=parser.clone();//depends on implementation
...
}
As far as I can see, there is no need to parse twice. Just parse it once into an object of type ResultEntitiesContainer and count the elements in the list to get count. You could change method parseEntities as follows:
void parseEntities(JsonParser parser) {
ObjectMapper om = new ObjectMapper();
// Need to parse the entities in the json node
ResultEntitiesContainer rec = om.readValue(parser, ResultEntitiesContainer.class);
// Need to extract number of entities.
int count = rec.getResultEntities().size();
}
Alternatively you can parse to object ResultEntitiesContainer from json node as follows:
ResultEntitiesContainer rec = om.treeToValue(node, ResultEntitiesContainer.class);
Remark:
Please double check if ResultEntitiesContainer should be static.
find-bug shows String can't cast to util.Map
When run through the application getting correct result.
siteList looks [{site_id=47, site_code=a}, {site_id=48, site_code=ABC}, {site_id=49, site_code=ABCD}, {site_id=54, site_code=ABCE}, {site_id=1, site_code=FXGL}]
public static List<SitesDto> transformSites(List<String> siteList) {
List<SitesDto> sitList = new ArrayList<>();
for (Object object : siteList) {
Map row = (Map) object;----->showing error
final SitesDto site = new SitesDto();
String code = (String) row.get(SITE_CODE);
Object id = row.get(SITE_ID);
site.setSiteId(((Number) id).longValue());
site.setSiteCode(code);
sitList.add(site);
}
return sitList;
}
find-bug shows String can't cast to util.Map
The java.util.Map interface represents a mapping between a key and a value.
Where as, siteList is a collection of Strings (they are not key and value pairs which Map is expecting), so is the error String can't cast to util.Map.
The List passed in parameter should be of List<Map<String,Object>>
Hope it helps.
You declare a method that accepts a List<String> argument. Since you say that the application runs without any apparent error, it would appear that the siteList argument is not actually a List<String> at all, but instead is a List<Map>. You should have got a findbugs warning somewhere else, probably in the method that calls transformSites, saying that you shouldn't be using raw types or that a List<Map> shouldn't be passed to a method that takes a List<String> argument. You should fix that first. You should also avoid using raw types: use List<whatever> instead of just List, and Map<String, Number> instead of just Map. Otherwise, you are making it easier to make errors.
I'm wondering if there is a way to Auto Cast an Object to some type by storing the Class type along with the object? I thought this was possible with Java, but maybe not.
For example:
class StorageItem
{
private int itemcount;
StorageItem(int itemcount)
{
this.itemcount = itemcount;
}
int getItemCount()
{
return itemcount;
}
}
class Storage
{
private Class clazz;
private Object value;
public Storage(Class clazz, Object value)
{
this.clazz = clazz;
this.value = value;
}
//Is there a way such a call can be created to automatically cast
//the object to the class type and return that cast type in a
//generic way. The idea being that Storage knows what it should
//already be cast to. Is this possible?
public T getValue()
{
return clazz.cast(value);
}
}
A usage example:
public static void main(String[] args)
{
//Create storage item
Storage storage = new Storage(StorageItem.class, new StorageItem(1234));
//The call to getValue() will automatically cast to the Class passed
//into Storage.
int itemcount = storage.getValue().getItemCount(); //returns 1234
}
Obviously the getValue() call in Storage is a pseudocode call, but it's just there to provide the idea as to what I would like to do.
Is there anyway to have a getValue() call that will Auto cast to the Class typed stored in the Storage class. Again, the idea is that the Storage class knows what it should be cast to. Or is there anyway this can be done at all?
StorageItem is just one simple example. Here, it just stores an int for discussion purposes. However, it could be more complex.
Another usage example, would be storing the Storage object in a list.
List<Storage> row = new ArrayList<Storage>();
row.add(new Storage(StorageItem.class, 1234));
row.add(new Storage(String.class, "Jason"));
row.add(new Storage(Integer.class, 30));
row.add(new Storage(Double.class, 12.7));
Then, these can be accessed in the following way.
//calls StorageItem's getItemCount() method
row.get(0).getValue().getItemCount(); //returns 1234
//calls String's length() method
row.get(1).getValue().length(); //returns 5
//calls Integer's intValue() method
row.get(2).getValue().intValue();
//calls Integer's doubleValue() method
row.get(3).getValue().doubleValue();
If getValue() only ever returned an Object, I would have to always cast to the specific Object manually. Instead, if I can store the cast class inside the Storage object, then Storage has enough information to know what to automatically cast the Object to on the getValue() call.
If this is doable in Java is the answer to the question I'm seeking. And if so, how?
Would this do the trick? Much less hacking is required:
class Storage<T> {
private T value;
public Storage(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
I don't really see the problem that you're trying to solve here. #bali182's answer does give you a "generic" way to store a reference - but storing the reference itself is just easier.
Consider what happens if you put two Storage instances, containing differently-typed references, into a collection:
List<Storage<SOMETHING>> storages = new ArrayList<>();
storages.add(new Storage<String>("Hello"));
storages.add(new Storage<Integer>(1));
So: what is SOMETHING? Well, it has to be ?, since that is the only type which satisfies both elements.
Now, when you iterate through the list to retrieve them, you have to deal with them as Object:
for (Storage<?> storage : storages) {
Object object = storage.getValue();
// ...
}
because you don't, in general, know what the type of the stored reference is for any given element. The concrete type of object will be the concrete type of the element - String and Integer, for the list above - but you can't make use of these different types without using some means to detect that type (e.g. if (object instanceof String)).
It would just have been easier if the references were stored directly in the list:
List<Object> objects = new ArrayList<>();
storages.add("Hello");
storages.add(1;
for (Object object : objects) {
// ...
}
You still would have to do something to detect the concrete type of object; you're just doing it without the extra layer of indirection.
Although the above example is for unrelated types, it's still easier to do it with the direct references if they are the same type:
List<String> objects = Arrays.asList("Hello", "World");
for (String object : objects) {
// ...
}
Now you don't need to do anything to know the concrete type (you might, if the elements were of a non-final class, and you wanted to handle some subclasses specially), but you've still avoided needing to dereference Storage.getValue() to get at a value that you could have directly instead.
Running my code results in the following error: java.lang.Integer cannot be cast to java.lang.String
This is what I have:
for(Map<String, Object> record: list) {
if(((String)record.get("level")).equals("1")) {
rootList.add(record);
}
}
I have tried converting that little snippet to an Int using toString but it just gives me a compiling error. I'm new to java (and I did not write this code either) so forgive me if it's a stupid mistake.
Your Maps holds values of type Object, meaning they can really hold anything as values (since everything extends Object); there's no guarantee that a given value will be a String. By your error, it would appear that one of the values in one of your maps is an Integer, which evidently cannot be cast to a String. The quick-fix is to use toString:
record.get("level").toString().equals("1")
But note that this will yield true for any object whose toString returns "1", not just strings. If you want to only check for equality with the string "1", then you can use "1".equals(...), since equals can take any object as an argument.
try
Object number = record.get("level");
if("1".equals(number)){
}
try one of this alternatives:
rootList.add(record + "");
or
rootList.add(new String(record));
Well, if the Map values are Integers, then toString() should work for converting them to strings:
for(Map<String, Object> record: list) {
if(record.get("level").toString().equals("1")) {
rootList.add(record);
}
}
Attempting to de-serialize JSON to a Map<K, V> where K is a String and V one of several types (i.e String int or boolean). Using Gson, I tried the following code:
public static void main(String[] args) {
JsonObject json = new JsonObject();
json.addProperty("str", "str-value");
json.addProperty("int", 10);
json.addProperty("bool", true);
// "json" now contains {"str":"str-value","int":10,"bool":true}
Type mapType = new TypeToken<Map<String, Object>>() {}.getType();
Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(json, mapType);
String str = (String) map.get("str"); // cannot cast Object to String
str = String.valueOf(map.get("str")); // results in java.lang.Object#1632847
}
Technical Answer
If you attach the Gson source code and then debug the program you will find the source of the problem. The problem seems to rest in the Gson code.
Since the value is of type Object your call to fromJson(), the class com.google.gson.ObjectNavigator will be used and comes into this section of code [~line 113]
else if (objTypePair.type == Object.class && isPrimitiveOrString(objectToVisit)) {
visitor.visitPrimitive(objectToVisit); //Target value is the proper value after this call
visitor.getTarget(); //target value is now a blank Object.
where visitor is of type JSonObjectDeserializationVisitor, objectToVisit is your value and objectTypePair is the type it is to become (Object).
Now, The JsonObjectDeserializationVisitor has a flag called constructed which is set to false by default. so when the getTarget() method is called, it checks to see if the constructed flag is true, if not it creates a new instance of the object you are trying to create. since the constructed flag is never set the call to getTarget() returns a blank Object.
The call to getTarget() seems unnecessary in the code and appears to be the source of the problem. visitPrimitive performs the proper action by placing the parsed value into the target variable which would be passed back. This appears to be a bug surrounding the Object type.
This isn't something you could fix unless you are willing to do a custom build of Gson. I would recommend filing this as a report on the Gson forums.
There is already one report filed for version 1.5
Work Around
I see two ways you can fix this in the short term.
1) Make the Map of type <String, String>, though you lose the types you can still get the values back properly
2) Put the values into a bean class first and then Serialize that.
public class myObj{
String myStr;
int myInt;
boolean myBool;
//get and set methods
}
public static void main(String args[]){
MyObj o = new MyObj();
o.setMyStr("str-value");
Gson gson = new Gson();
String json = gson.toJson(o);
o = gson.fromJson(json, MyObj.class);
}
Both defeat the purpose of what you are trying to do, but it is in googles court to fix this bug I believe.