Gson conversion of a simple string fails with exception - java

I need to convert a json string to particular type based on the function's return type. The json string could also be a plain string. I am facing issues while converting the strings using gson.fromJson method.
Why is an exception thrown in gson.fromJson if the string contains a space? And how do I get around this?
import java.lang.reflect.Method;
import com.google.gson.Gson;
class Trial {
Object retu() {
return "BEVERLY OUTLAW";
}
}
public class sample {
public static void main (String args[]) {
Gson gson = new Gson();
Trial obj = new Trial();
Method func = obj.getClass().getDeclaredMethods()[0];
Object o = gson.fromJson("beverlyoutlaw", func.getGenericReturnType());
System.out.println(o); ---> this prints beverlyoutlaw
o = gson.fromJson("beverly outlaw", func.getGenericReturnType()); ---> This throws an exception!
System.out.println(o);
}
}

Strings in Json need to be surrounden by quotes. That is - in the Json itself!
Neither "beverlyoutlaw" nor "beverly outlaw" contain quotes in the string - the quotes are just part of the Java String literal.
Apparently Gson does not enforce this for strings without spaces - for strings with spaces however it is strictly necessary.
To fix your issue, use o = gson.fromJson("\"beverly outlaw\"", func.getGenericReturnType());

Related

Convert String (http json) to Object in java (automatic)

I'm a beginner in programming, and I need some help. Is it possible to convert an HTTP (that returns a Json) automatic call to object in java? For example it reads the request, and when I call System.out.println (obj) it already returns me an OBJECT of this request, instead of String. Is it possible? If so, could you help me ... I already did the method to call the url and return string, but I need to return OBJECT, so I can compare with HashCode and Equals.
My code:
enter image description here
output:
{"header":{"messageId":"02938ec7-b2c3-4131-8ecf-3ad3a8509b41"},"body":{"products"
What I wanted: output
Informacoes [header=Header [messageId=66d22c00-bddc-4ea7-afbd-7c7225fcb914], body=Body
From what I can understand from your question, it looks like Gson might be useful. Gson is a library that allows you to convert between JSON and Java primitives/objects. Here's an example I just wrote:
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
}
public static void main(String[] args) {
Gson gson = new Gson();
String json = "{\"value1\":1,\"value2\":\"abc\"}";
BagOfPrimitives obj = gson.fromJson(json, BagOfPrimitives.class);
}
This code converts the json {"value1":1,"value2":"abc"} into an object of the class BagOfPrimitives.
To add Gson to your project, go here, click "Downloads" at the top right, and click "jar". Then follow these instructions to add the jar file to your project. Then you should be able to write import com.google.gson.* at the top of your class and use Gson in your java code.

How can I deserialize a list of enums using Jackson JSON?

I'm working on a configuration system. I'd like to be able to load config values from a JSON file and have them "automagically" convert to the Java type I need. I'm using Jackson for the JSON parsing. For primitive types like floats and strings, it's no big deal, but I'm running into a snag with enums.
Let's say I have the following enum:
public enum SystemMode
{
#JsonProperty("Mode1")
MODE1("Mode1"),
#JsonProperty("Mode2")
MODE2("Mode2"),
#JsonProperty("Mode3")
MODE3("Mode3");
private final String name;
private SystemMode(String name)
{
this.name = name;
}
#Override
#JsonValue
public String toString()
{
return this.name;
}
}
Now, let's say I want to represent a list of values of this enum for a given config variable using the following JSON representation:
{
"Project" : "TEST",
"System" : {
"ValidModes" : ["Mode1", "Mode2"]
}
}
And I'd like to be able to do something like the following:
ArrayList<SystemMode> validModes = (ArrayList<SystemMode>) configurator.getConfigValue("/System/ValidModes");
For reference, my configurator class's getConfigValue method is essentially a thin wrapper over the Jackson JSON parsing:
public Object getConfigValue(String JSON_String)
{
JsonNode node = JsonNodeFactory.instance.objectNode().at(JSON_String);
return objectMapper.convertValue(node, Object.class);
}
(The real method has some exception checking that has been omitted for clarity).
Now, when I call the above, Jackson correctly deduces that I want an ArrayList and fills it. However, instead of getting an ArrayList of SystemMode enums, I get an ArrayList of Strings and immediately throw an exception when I attempt to use the list. I have tried several different ways of representing the data to no avail. It seems no matter what I try, Jackson wants to return a list of strings instead of a list of enums.
So my question is this:
How can I make Jackson (version 2.9.4) JSON properly deserialize a list of enum values in a way that is compatible with my single "Object getConfigValue()" method?
The following will provide the correct binding for your enum.
public List<SystemMode> getConfigValue(String path)
{
JsonNode node = JsonNodeFactory.instance.objectNode().at(path);
return objectMapper.convertValue(node, new TypeReference<List<SystemMode>>(){});
}
The second option is to convert the list of String yourself, for example:
List<SystemMode> result = jsonResult.stream().map(SystemMode::valueOf).collect(Collectors.toList());
Third option:
public <T>List<T> getConfigValue(String path, Class<T> type)
{
JsonNode node = JsonNodeFactory.instance.objectNode().at(path);
CollectionType toType =
objectMapper.getTypeFactory().constructCollectionType(List.class, type);
return objectMapper.convertValue(node, toType);
}

Serializing Gson single element array as a simple String

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);
}
}

Parsing JSON array with gson

I am having trouble parsing my JSON which i get from javascript.
The format of JSON is this:
[{"positions":[{"x":50,"y":50},{"x":82,"y":50},{"x":114,"y":50},{"x":146,"y":50}]},{"positions":[{"x":210,"y":50},{"x":242,"y":50},{"x":274,"y":50}]}]
So far i have been able to get this far:
{"positions":[{"x":50,"y":50},{"x":82,"y":50},{"x":114,"y":50},{"x":146,"y":50}]}
But i also need to now create a class with those positions. I havnt been working on the class, since i tried printing out the output first, but i am unable to break it down further. I am getting this error message:
java.lang.IllegalStateException: This is not a JSON Array.
And my code is this:
JsonParser parser = new JsonParser();
String ships = request.getParameter("JSONships");
JsonArray array = parser.parse(ships).getAsJsonArray();
System.out.println(array.get(0).toString());
JsonArray array2 = parser.parse(array.get(0).toString()).getAsJsonArray();
System.out.println(array2.get(0).toString());
I have also tried to do it this way:
Gson gson = new Gson() ;
String lol = (gson.fromJson(array.get(0), String.class));
System.out.println(lol);
In which case i get:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected STRING but was BEGIN_OBJECT
In the end, i want to loop through positions, creating class for each "positions", which contains a List with another class Position, which has the int x, y.
Thank you for your time.
Define your classes and you will get everything you need using gson:
public class Class1 {
private int x;
private List<Class2> elements;
}
And the inner class:
public class Class2 {
private String str1;
private Integer int2;
}
Now you can parse a json string of the outer class just like that:
gson.fromJson(jsonString, Class1.class);
Your error while using Gson is that you try to parse a complex object in String, which is not possible.

GSON: How to get a case insensitive element from Json?

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.

Categories