I like to use GSON to deserialize the following JSON String.
{
"type": "FeatureCollection",
"features": [
{
"id": "FSROGD.4440181",
"geometry": {
"type": "Point",
"coordinates": [
16.7594706041998,
43.148716514354945
]
}
}
]
}
I already prepared the neccessary Java classes named Response, Features, Geometry and Coordinates. Besides the last class everything works fine. But for Coordinates, I do not understand what I should write since there are no keys given that I could prepare as member variables.
Here are the parent Geometry class ...
package info.metadude.trees.model.vienna;
import com.google.gson.annotations.SerializedName;
public class Geometry {
#SerializedName("type")
public String type;
// TODO: Prepare Coordinates class for GSON.
// #SerializedName("coordinates")
// public Coordinates coordinates;
}
... and the empty Coordinates class.
package info.metadude.trees.model.vienna;
public class Coordinates {
// TODO: No idea what should be defined here.
}
You could use coordinates as a collection property in Geometry. This would automatically map the values to the right property.
import java.util.List;
import com.google.gson.Gson;
public class GSonTest {
public static void main(final String[] args) {
Gson gson = new Gson();
System.out.println(gson.fromJson("{ \"type\": \"Point\", \"coordinates\": [ 16.7594706041998, 43.148716514354945 ] }", Geometry.class));
}
public static class Geometry {
List<Float> coordinates;
public List<Float> getCoordinates() {
return coordinates;
}
public void setCoordinates(final List<Float> coordinates) {
this.coordinates = coordinates;
}
#Override
public String toString() {
return "Geometry [coordinates=" + coordinates + "]";
}
}
}
Related
I have a JSON that looks more or less like this:
{
"modules": {
"someExistingModule": {
"name": "pug",
...
},
"randomExistingModule": {
"type": "cat",
...
},
"myNewModule": { // <----- I care about this module. Note that this is NOT an array
"modules": {
"img1": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"img2": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"img3": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"txt1": { // <------ Updated JSON
"type": "text",
"content": "Hello world 1"
},
"txt2": {
"type": "text",
"content": "Hello world 2"
},
...
}
}
Inside myModule there can be N number of imgN objects and txtN. I need to parse this dynamically.
My current Response class looks like this:
public class MyModuleResponse extends SomeResponseClass
{
#Override
public void parse(InputStream data)
{
T responseBody = readJsonStream(data, MyModuleResponseBody.class());
MyModuleDataParser.parse(responseBody);
}
MyModuleDataParser.java
...
public static MyModuleDataParser parse(#Nullable MyModuleResponseBody body)
{
parseSomeExistingModule();
parseRandomExistingModule();
parseMyNewModule(); // <--- This is the new module I'm trying to parse. Currently, this method is empty.
}
MyModuleResponseBody.java
public class MyModuleResponseBody
{
public Modules modules;
public static class Modules
{
SomeExistingModule someExistingModule;
RandomExistingModule randomExistingModule;
MyNewModule myNewModule; // <----- My new module
}
public static class SomeExistingModule
{
String name;
...
}
public static class RandomExistingModule
{
String type;
...
}
public static class MyNewModule
{
public ??? modules; // <--- Trying to define the Type here. Something like List<MyImageModule>. But, it won't work
}
MyImageModule.java
public class MyImageModule extends Module // <---- Update: This class now extends a generic Module class
{
private String url;
private String altText;
}
MyTextModule.java <---- New Module
public class MyTextModule extends Module // New class
{
private String content;
}
Module.java
public class Module // <----- New parent class
{
protected String type;
}
How do I create a list of MyImageModule from myNewModule? I believe I need to use some kind of TypeAdapter from Gson library. But, I'm not familiar how to do this inside an existing response.
Use Map<String, MyImageModule>, in fact, a hashmap to solve the issue of non-list modules object in the json.
public static class MyNewModule {
public Map<String, MyImageModule> modules; // initialize as a hashmap
}
My lib is calling an API which can return either of the following JSON structure -
{
"key_is_same" : {
"inner" : "val"
}
}
-or-
{
"key_is_same" : [{
"inner" : "val1"
},
{
"inner" : "val2"
}
]
}
Is there any annotation in jakson which can handle this and deserializ it into respective type
Looks like you are looking for the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays, java.util.Collection) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array. This feature is meant to be used for compatibility/interoperability reasons, to work with packages (such as XML-to-JSON converters) that leave out JSON array in cases where there is just a single element in array.
Feature is disabled by default.
It could be enabled either in ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Or via the #JsonFormat annotation:
#JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Foo> oneOrMany;
For illustration purposes, consider the following JSON documents:
{
"oneOrMany": [
{
"value": "one"
},
{
"value": "two"
}
]
}
{
"oneOrMany": {
"value": "one"
}
}
It could be the deserialized to the following classes:
#Data
public class Foo {
private List<Bar> oneOrMany;
}
#Data
public class Bar {
private String value;
}
Just ensure the feature is enabled in your ObjectMapper or your field is annotated with #JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).
And in case you are looking for the equivalent feature for serialization, refer to WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED.
I would recommend using Object as your data type for the property which is dynamic. So Here is my sample.
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MainObject {
private Object key_is_same;
public Object getKey_is_same() {
return key_is_same;
}
public void setKey_is_same(Object key) {
this.key_is_same = key;
}
public static class KeyObject {
private String inner;
public String getInner() {
return inner;
}
public void setInner(String inner) {
this.inner = inner;
}
}
public static void main(String...s) throws JsonProcessingException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
public static void main(String...s) throws IOException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
// Deserialize
MainObject mainWithObject = om.readValue("{\"key_is_same\":{\"inner\":\"val1\"}}", MainObject.class);
MainObject mainWithList = om.readValue("{\"key_is_same\":[{\"inner\":\"val1\"},{\"inner\":\"val1\"}]}", MainObject.class);
if(mainWithList.getKey_is_same() instanceof java.util.List) {
((java.util.List) mainWithList.getKey_is_same()).forEach(System.out::println);
}
}
}
}
Output
{"key_is_same":{"inner":"val1"}}
{"key_is_same":[{"inner":"val1"},{"inner":"val1"}]}
I have a JSON string which looks like this:
{
"status": "status",
"date": "01/10/2019",
"alerts": {
"labels": {
"field1": "value1",
"field2": "value2",
"field3": "value3",
"field100": "value100"
},
"otherInfo" : "other stuff"
},
"description": "some description"
}
My corresponding Java classes look like the following:
public class Status {
private String status;
private String date;
private Alerts alerts;
private String description;
}
And
public class Alerts {
private Map<String, String> labels;
private String otherInfo;
public Map<String, String> getLabels() {
return labels();
}
}
I'm parsing the given JSON into Java object using this:
Status status = gson.fromJson(statusJSONString, Status.class);
This also gives me Alerts object from Status class:
Alerts alerts = status.getAlerts();
Here is my problem:
Let's consider the labels:
I want to make keys in the label map the case-insensitive. So for example, if the provided key/value pair is "field1" : "value1", or "Field1" : "value1" or "fIeLD1":"value1", I want to be able to retrieve them by simply calling alerts.getLabels.get("field1").
Ideally, I want to set the keys to be lowercase when the labels map is originally created. I looked into Gson deserialization examples, but I'm not clear exactly how to approach this.
There isnt really much you can do here. Even if you extended HashMap, the problem is that when the JSON is de-serialized, it doesn't call native methods. What you COULD do is the following, but it is rather cumbersome:
import java.util.HashMap;
public class HashMapCaseInsensitive extends HashMap<String, String> {
private boolean convertedToLower = false;
#Override
public String put(String key, String value) {
if(!convertedToLower){
convertToLower();
}
return super.put(key.toLowerCase(), value);
}
#Override
public String get(Object key) {
if(!convertedToLower){
convertToLower();
}
return super.get(key.toString().toLowerCase());
}
private void convertToLower(){
for(String key : this.keySet()){
String data = this.get(key);
this.remove(key);
this.put(key.toLowerCase(), data);
}
convertedToLower = true;
}
}
You can write your own MapTypeAdapterFactory which creates Map always with lowered keys. Our adapter will be based on com.google.gson.internal.bind.MapTypeAdapterFactory. We can not extend it because it is final but our Map is very simple so let's copy only important code:
class LowercaseMapTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
TypeAdapter<String> stringAdapter = gson.getAdapter(TypeToken.get(String.class));
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) { }
#Override
public T read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
Map<String, String> map = new HashMap<>();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
String key = stringAdapter.read(in).toLowerCase();
String value = stringAdapter.read(in);
String replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
}
in.endObject();
return (T) map;
}
};
}
}
Now, we need to inform that our Map should be deserialised with our adapter:
class Alerts {
#JsonAdapter(value = LowercaseMapTypeAdapterFactory.class)
private Map<String, String> labels;
private String otherInfo;
// getters, setters, toString
}
Assume that our JSON payload looks like below:
{
"status": "status",
"date": "01/10/2019",
"alerts": {
"labels": {
"Field1": "value1",
"fIEld2": "value2",
"fielD3": "value3",
"FIELD100": "value100"
},
"otherInfo": "other stuff"
},
"description": "some description"
}
Example usage:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class GsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
Gson gson = new GsonBuilder().create();
Status status = gson.fromJson(new FileReader(jsonFile), Status.class);
System.out.println(status.getAlerts());
}
}
Above code prints:
Alerts{labels={field1=value1, field100=value100, field3=value3, field2=value2}, otherInfo='other stuff'}
This is really tricky solution and it should be used carefully. Do not use this adapter with much complex Map-es. From other side, OOP prefers much simple solutions. For example, create decorator for a Map like below:
class Labels {
private final Map<String, String> map;
public Labels(Map<String, String> map) {
Objects.requireNonNull(map);
this.map = new HashMap<>();
map.forEach((k, v) -> this.map.put(k.toLowerCase(), v));
}
public String getValue(String label) {
return this.map.get(label.toLowerCase());
}
// toString
}
Add new method to Alerts class:
public Map<String, String> toLabels() {
return new Labels(labels);
}
Example usage:
status.getAlerts().toLabels()
Which gives you a very flexible and secure behaviour.
Though this is not a very generic solution, however, I think this will serve your purpose.
I would like to suggest you create an adapter for Gson which can convert the map values for you. The adapter might look like the following.
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
final class GSONAdapter implements JsonDeserializer<String> {
private static final GSONAdapter instance = new GSONAdapter();
static GSONAdapter instance() {
return instance;
}
#Override
public String deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
// Here I am taking the elements which are starting with field
// and then returning the lowercase version
// so that the labels map is created this way
if (jsonElement.getAsString().toLowerCase().startsWith("field"))
return jsonElement.getAsString().toLowerCase();
else return jsonElement.getAsString();
}
}
Now just add the GsonBuilder to your Gson using the adapter and then try to parse the JSON. You should get all the values in the lower case as you wanted for the labels.
Please note that I am just taking the field variables in my concern and hence this is not a generic solution which will work for every key. However, if your keys have any specific format, this can be easily applied.
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, GSONAdapter.instance())
.create();
Status status = gson.fromJson(statusJSONString, Status.class);
Alerts alerts = status.getAlerts();
Hope that solves your problem.
I have nested objects that I want to get the root name from when I serialize it to json using jackson. this is the result I want(I apologize to all the flower enthusiasts but just needed a example, this is not to be interpreted as accurate data):
{
"plants": "flowers",
"types": {
"rose" : {
"color": "red",
"height": 25,
}
}
}
I got 2 classes, one wrapper class:
public class JsonWrapper {
public String plants = "flowers";
public Object types;
}
and the "flower" class:
#JsonRootName("rose")
public class rose{
public String color = "red";
public int height = 25;
}
now what I do is this:
JsonWrapper wrapper = new JsonWrapper();
wrapper.types = new rose();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
String jsonInString = mapper.writeValueAsString(wrapper);
The result i get is:
{
"JsonWrapper":{
"plants": "flowers",
"types": {
"color": "red",
"height": 25,
}
}
}
So i get the root name for the wrapper object that is not annotated but not for the child object that is annotated. is there a way to tell jackson not to get a root name where there is none and also analyze child objects?
An alternate solution without JsonRootName.
JsonWrapper.java
import java.util.HashMap;
import java.util.Map;
public class JsonWrapper {
public String plants = "flowers";
Map<String, Flower> types = new HashMap<>();
public Map<String, Flower> getTypes() {
return types;
}
public void setTypes(Map<String, Flower> types) {
this.types = types;
}
}
Flower.java
public class Flower {
public String color = "red";
public int height = 25;
}
Main.java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws JsonProcessingException {
JsonWrapper wrapper = new JsonWrapper();
wrapper.getTypes().put("rose", new Flower());
ObjectMapper mapper = new ObjectMapper();
String jsonInString = mapper.writeValueAsString(wrapper);
System.out.println(jsonInString);
}
}
I want to convert this JSON into objects in java:
{
"mapping": [
{
"boardPosition": {
"row": 1,
"col": 1
},
"nodeId": 3242324
},
{
"boardPosition": {
"row": 1,
"col": 2
},
"nodeId": 432423
},
{
"boardPosition": {
"row": 1,
"col": 3
},
"nodeId": 424324132
}
]
}
this is how I created my java classes
class MapeoWumpus {
public mapp mapping;
}
class mapp{
public boardP boardPosition;
public String nodeId;
}
class boardP{
public int row;
public int col;
}
and then when I try to convert my file like this
MapeoWumpus mapa=new MapeoWumpus();
mapa=gson.fromJson(filetext, MapeoWumpus.class);
I get a null object
What can I do?
EDIT: This is my entire code:
package parserjson;
import java.io.FileNotFoundException;
import java.util.*;
import com.google.gson.*;
public class Main {
/**
* #param args
*/
public static void main(String[] args) throws FileNotFoundException {
String filetext;
ParserJson parser=new ParserJson();
Gson gson=new Gson();
MapeoWumpus mapa=new MapeoWumpus();
filetext=parser.leerArchivo("b1.json");
mapa=gson.fromJson(filetext, MapeoWumpus.class);
}
}
"leerArchivo" is just a method to get the json file, as you can see my json file is in a string variable
You should define instance variable mapp as array. Because your JSON data seems to contain mapping array.
class MapeoWumpus {
public mapp[] mapping;
}
Creating new MapeoWumpus in the below code is unnecessary
MapeoWumpus mapa=new MapeoWumpus();
mapa=gson.fromJson(filetext, MapeoWumpus.class);
Just change it as follows
MapeoWumpus mapa=gson.fromJson(filetext, MapeoWumpus.class);