Calling method with generics - java

I'm working with a service which returns JSON which can be converted to Map (I'm using google-gson lib for converting). I need to get Set of values from this Map.
First, I had the next structure:
public Set<ProfileShow> getShows() {
String json = ...; //getting JSON from service
if (!Utils.isEmptyString(json)) {
Map<String, ProfileShow> map = Utils.fromJSON(json, new TypeToken<Map<String, ProfileShow>>() {
}.getType());
Set<ProfileShow> result = new HashSet<ProfileShow>();
for (String key : map.keySet()) {
result.add(map.get(key));
}
return result;
}
return Collections.emptySet();
}
public Set<Episode> getUnwatchedEpisodes() {
String json = ...; //getting JSON from service
if (!Utils.isEmptyString(json)) {
Map<String, Episode> map = Utils.fromJSON(json, new TypeToken<Map<String, Episode>>() {
}.getType());
Set<Episode> result = new HashSet<Episode>();
for (String key : map.keySet()) {
result.add(map.get(key));
}
return result;
}
return Collections.emptySet();
}
Utils.fromJSON method:
public static <T> T fromJSON(String json, Type type) {
return new Gson().fromJson(json, type);
}
As you can see, methods getShows() and getUnwatchedEpisodes() has the same structure. Only difference is a parametrized type of the returning Set. So, I decided to move getting Set from JSON to util method:
public static <T> Set<T> setFromJSON(String json, T type) {
if (!isEmptyString(json)) {
Map<String, T> map = fromJSON(json, new TypeToken<Map<String, T>>() {
}.getType());
Set<T> result = new HashSet<T>();
for (String key : map.keySet()) {
result.add(map.get(key));
}
return result;
}
return Collections.emptySet();
}
But now I'm stuck how to call this method in a proper way. Something like
Utils.setFromJSON(json, Episode.class.getGenericSuperclass()); //doesn't work
Thanks for your help.

Perhaps the easiest thing to do is change the type of type to Type, and pass in new TypeToken<Map<String, ProfileShow>>() { }.getType() or similar.
I guess you could construct the ParameterizedType if you really wanted.

Related

How can I transform a Map with a value of Object to the appropriate type?

I am working on a project where I need to accept a Map called properties that is of type Map<String, Object>. There are going to be potentially many different keys in this Map, but I only care about one: xpath. An xpath can have one of three different types of values:
A string, such as {"xpath": "path/to/xml/tag"}
A List of xpaths, such as: {"xpath": ["path/to/xml/tag1", "tag2", "path/tag3"}
A Map<String, Map<String, Boolean>>, such as:
{
"xpath":
{
"path/to/xml":
{
"setting1?": true,
"setting2?": true
},
"path/tag2":
{
"setting1?": false,
"setting2": true
},
"path/to/tag3": null
}
}
Now I have three variables: String xpath, Set<String> xpaths, Map<String, Map<String, boolean> xpathMap. I have a function that is supposed to try and map the values of the "xpath" key in the properties map, and it looks like this:
private void decideXPathType(Map<String, Object> properties)
{
Object propertiesXPath = properties.get("xpath");
if (propertiesXPath instanceof String)
{
this.xpath = (String) propertiesXPath;
} else if (propertiesXPath instanceof List)
{
this.xpaths = new HashSet<String>((List) propertiesXPath);
} else if (propertiesXPath instanceof Map)
{
for (Object key : ((Map) propertiesXPath).keySet())
{
Map<String, Boolean> value = (Map<String, Boolean>) ((Map) propertiesXPath).get(key);
this.xpathMap.put((String) key, value);
}
} else
{
throw new IllegalArgumentException("the xpath value is neither String, List, or Map<String, Boolean>");
}
}
But this function looks so bad - there is so much casting, etc - and although it works, it just looks too messy, and I imagine something can go wrong... any ideas on how I can make this cleaner?
Edit: Some more details
The properties map is originally a json JsonNode requestBody that I receive from a service. Using ObjectMapper, I create a properties map as such:
Map<String, Object> properties = new ObjectMapper().convertValue(new ObjectMapper().readTree(requestBody), new TypeReference<Map<String, Object>>(){});
If I receive a json string that is the value of the xpathMap example that I gave, I get something that looks like this:
Hope this information helps?
In your JSON, use different keys for these different types of values: String, List and Map. Deserializing a map:
#Test
public void test0() throws IOException {
ObjectMapper om = new ObjectMapper();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("xpath-object.json");
JsonNode jsonNode = om.readTree(inputStream);
Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.get("xpath").toString(), Map.class);
// prints {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
System.out.println(value);
}
If you need to work with 3rd party JSON, you can use following approach:
#Test
public void test() throws IOException {
testThemAll("xpath-scalar.json");
testThemAll("xpath-array.json");
testThemAll("xpath-object.json");
// prints:
// path/to/xml/tag
// [path/to/xml/tag1, tag2, path/tag3]
// {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
}
private void testThemAll(String fileName) throws IOException {
ObjectMapper om = new ObjectMapper();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
JsonNode jsonNode = om.readTree(inputStream).get("xpath");
if (jsonNode.isValueNode())
System.out.println(jsonNode.asText());
else if (jsonNode.isArray()) {
System.out.println(om.readValue(jsonNode.toString(), List.class));
} else if (jsonNode.isObject()) {
Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.toString(), Map.class);
System.out.println(value);
}
}

Extract and copy values from JSONObject to HashMap

I am having a JSON which contains upto 1000 Keys. I need some specific keys out of it.
Rather than traversing through the JSON and finding key and put its value in required parameter.
I thought of doing it in other way.
I am creating a HashMap of the keys I need.
Now i want to pass a JSONObject through it, where if we find the keys in JSONObject, it will automatically update the HashMap with the required keys.
Is there some function given by Spring where we can do it easily or do I Have to loop through it.
For Example:
JSONObject:-
{
"a":"a",
"b":"b",
"c":"c",
"d":"d",
"e":"e",
}
HashMap that I created :-
Map<String, Object> keys = new HashMap<>();
keys .put("a", "");
keys .put("b", "");
I want a function where i would pass two params
function HashMap mapJsonToHashMap(HashMap, JSONObject) {
}
Returned HashMap would be :-
{
"a":"a",
"b":"b"
}
IMO 1000 keys are not a big deal, so I would go for a simple solution of deserialize it to an object, then just filter/map using streams. Something like:
ObjectMapper objectMapper = new ObjectMapper();
Set<String> keys = new HashSet<>();
keys.add("key-1");
keys.add("key-3");
List<Parameter> list =
objectMapper.readValue("[{ \"key\":\"key-1\", \"value\":\"aaa\" }, { \"key\":\"key-2\", \"value\":\"bbb\" }, { \"key\":\"key-3\", \"value\":\"ccc\" }]", new TypeReference<List<Parameter>>(){});
List<Parameter> filteredList = list.stream()
.filter(l -> keys.contains(l.getKey()))
.collect(Collectors.toList());
// in case you really want to put results in a Map
Map<String, String> KeyValueMap = filteredList.stream()
.collect(Collectors.toMap(Parameter::getKey, Parameter::getValue));
public class Parameter
{
private String key;
private String value;
public String getKey()
{
return key;
}
public void setKey(String key)
{
this.key = key;
}
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
}
You can try something like this,
Convert JSON to HashMap
Then remove unwanted entries from the converted map
public HashMap mapJsonToHashMap(HashMap keys, JSONObject json) {
// Convert JSON to HashMap
ObjectMapper mapper = new ObjectMapper();
Map<String, String> jsonMap = mapper.readValue(json, Map.class);
// Iterate jsonMap and remove invalid keys
for(Iterator<Map.Entry<String, String>> it = jsonMap .entrySet().iterator();
it.hasNext(); ) {
Map.Entry<String, String> entry = it.next();
if(!keys.containsKey(entry.getKey())) {
it.remove();
}
}
return jsonMap;
}

Loading Hashmap Through JSON

Hashmap:
public HashMap<Perk, Boolean> perks;
public HashMap<Perk, Boolean> getPerks() {
return perks;
}
How I save (works fine, I'm using GSON by Google):
object.add("perks", builder.toJsonTree(player.perks));
Preview of save:
"perks": {
"DAMAGE_AMPLIFIER": true
}
How I am attempting to load but isn't working, error is below.
if (reader.has("perks")) {
jsonToMap(reader.get("perks").toString(), player);
}
jsonToMap method:
public static void jsonToMap(String t, Player player) throws JSONException {
JSONObject jObject = new JSONObject(t);
Iterator<?> keys = jObject.keys();
while(keys.hasNext()) {
String key = keys.next().toString();
boolean value = jObject.getBoolean(key);
player.perks.put(Perk.valueOf(key), value);
}
}
Error:
java.lang.NullPointerException
at com.callisto.world.entity.impl.player.PlayerLoading.jsonToMap(PlayerLoading.java:730)
at com.callisto.world.entity.impl.player.PlayerLoading.getResult(PlayerLoading.java:684)
If I have to guess perks is null and that's why when you do put you get a null pointer. Change it to:
public HashMap<Perk, Boolean> perks=new HashMap<>(); //We init the hashmap here so we can putt stuff after that
public HashMap<Perk, Boolean> getPerks() {
return perks;
}
Also you might even change it to :
Map<Perk, Boolean> perks=new HashMap<>(); and use the interface and not the implementation in your methods

Parsing JSON keys as field value

I have a translation JSON object that maps locales to messages and takes the following form:
{"en_US" : "English Text", "sp": "Spanish Text", "fr" : "French Text", ... }
Is there a way for me to map the JSON object as a list of the following class using gson?
class Translation {
String locale, text;
}
I know I can parse it first as a map and then going through the map elements to create the Translation objects, but I'm not sure if there's a "gson" way of doing that.
There are two options. If you need to serialize and deserialize the data, you could write a custom TypeAdapter.
class TranslationTypeAdapter extends TypeAdapter<List<Translation>> {
#Override
public void write(JsonWriter out, List<Translation> list) throws IOException {
out.beginObject();
for(Translation t : list) {
out.name(t.locale).value(t.text);
}
out.endObject();
}
#Override
public List<Translation> read(JsonReader in) throws IOException {
List<Translation> list = new ArrayList<>();
in.beginObject();
while(in.hasNext()) {
list.add(new Translation(in.nextName(), in.nextString()));
}
in.endObject();
return list;
}
}
and then:
TypeToken typeToken = new TypeToken<List<Translation>>(){};
Type type = typeToken.getType();
Gson gson = new GsonBuilder().registerTypeAdapter(type, new TranslationTypeAdapter()).create();
List<Translation> list = gson.fromJson(new FileReader(new File("json")),type);
which outputs:
[Translation{locale='en_US', text='English Text'}, Translation{locale='sp', text='Spanish Text'}, Translation{locale='fr', text='French Text'}]
If, however, you only need to deserialize the data, you can just write a custom deserializer:
class TranslationDeserializer implements JsonDeserializer<List<Translation>> {
#Override
public List<Translation> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<Translation> list = new ArrayList<>();
for(Map.Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
list.add(new Translation(entry.getKey(), entry.getValue().getAsString()));
}
return list;
}
}
You register this deserializer with the GsonBuilder as in the first example. This yield the same output of course.

Converting XML to Java Map<String, Integer> using XStream

I'm converting XML code to a Java Map. The XML matches a large number of random words with a number (a probability distribution) and looks like this:
<?xml version="1.0" encoding="UTF-8" ?>
<root>
<Durapipe type="int">1</Durapipe>
<EXPLAIN type="int">2</EXPLAIN>
<woods type="int">2</woods>
<hanging type="int">3</hanging>
<hastily type="int">2</hastily>
<localized type="int">1</localized>
.......
</root>
I'm trying to implement this with XStream. Here's the Java code that my main program currently uses:
XStream xstream = new XStream();
Map<String, Integer> englishCorpusProbDist;
xstream.registerConverter(new MapEntryConverter());
englishCorpusProbDist = (Map<String, Integer>)xstream.fromXML(new File("C:/Users/David Naber/Documents/IREP Project/frequencies.xml"));
And here's my MapEntryConverterClass:
public class MapEntryConverter implements Converter {
public boolean canConvert(Class clazz) {
return Map.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
Map<String, Integer> map = (Map<String, Integer>) value;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
writer.startNode(entry.getKey().toString());
writer.setValue(entry.getValue().toString());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, Integer> map = new HashMap<String, Integer>();
while (reader.hasMoreChildren()) {
reader.moveDown();
map.put(reader.getNodeName(), reader.getValue());
reader.moveUp();
}
return map;
}
}
I"m getting an error in the above function, on the line "map.put(reader.getNodeName(), reader.getValue());". The error says: "The method put(String, Integer) in the type Map is not applicable for the arguments (String, String)."
So I really have two questions here. First of all, why is this error happening and how can I fix it? Secondly, what more will I need to implement to finally get XStream to convert this to XML?
Any help is much appreciated. Thank you in advance!
Yes error is correct reader.getValue() is giving String , You must have to Type Cast it in Integer
Change below code
map.put(reader.getNodeName(), reader.getValue());
to
map.put(reader.getNodeName(), new Integer(reader.getValue()));
This is my example for more complex data with nested Maps
public static class MapEntryConverter implements Converter {
static final Converter INSTANCE = new MapEntryConverter();
public boolean canConvert(Class clazz) {
return Map.class.isAssignableFrom(clazz);
}
public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
Map map = (Map) value;
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
writer.startNode(entry.getKey().toString());
Object val = entry.getValue();
if (val != null) context.convertAnother(val);
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
while (reader.hasMoreChildren()) {
reader.moveDown();
String key = reader.getNodeName();
Object value = null;
if (reader.hasMoreChildren()) {
value = unmarshal(reader, context);
} else {
value = reader.getValue();
}
map.put(key, value);
reader.moveUp();
}
return map;
}
}
Have fun!

Categories