I am trying to test my REST service by a JSON string from Chrome's Advanced REST Client. I have a nested JSON here. I am taking this as string and mapping it to my POJO class:
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(addressString, AddressPOJO.class);
Here, addressString holds the JSON String given below
{
"location":"[{\"Asia\":[{\"India\":[{\"city\":\"Bengaluru\"}]}], [{\"India\":[{\"city\":\"Mumbai\"}]}]}]
}
My AddressPOJO has variable:
Map<String,?> location = new HashMap();
I am retrieving the values from the POJO by
Map<String, ?> locations = addressPOJO.getLocation();
Iterator iterator1 = locations.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry pair1 = (Map.Entry)iterator1.next();
Map<String,?> cities = (Map<String,?>) pair1.getValue();
Iterator iterator2 = dataSets.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry pair2 = (Map.Entry)iterator2.next();
Map<String,?> city = (Map<String, ?>) pair2.getValue();
}
}
Here, I am only able to retrieve the second entry which is
[{\"India\":[{\"city\":\"Mumbai\"}]}]
I need to retrieve all the entries. I also tried to use MultiMap like this
MultiMap cities = (MultiMap) pair1.getValue();
But this is not accepted by compiler. Please note all the entries are dynamic in nature and the (key, value) pairs change as per user's input. Any suggestions how I can retrieve all the entries in this example.
From my understanding, maybe there are 2 things that you need to look into:
Why the data type of location is Map<String, ?>? Because from your JSON string, the type of location is an Array or a List, right? If you want to make it a Map, please use some strings like: {"location" : "\"key\":\"value\""}. If you want to make it a List, remove the "" around the value.
Another thing is that, it seems that you want a hierarchy to describe the some geography structure. Let's say, in Asia we have India and China, and in India we have Bengaluru and in China we have the city Chengdu. So the value of Asia should also be a List which contains two items India and China. So you should remove the ] and [ here, and I think this is also the reason why you are only able to retrieve the second entry.
Following is my testing code, I modified your JSON string and the data type of location.
Location.java
public class Location {
private List location;
public List getLocation() {
return location;
}
public void setLocation(final List location) {
this.location = location;
}
}
TestJSON.java
public class testJson {
private static ObjectMapper mapper = new ObjectMapper();
public static void main(final String[] args) throws JsonParseException, JsonMappingException, IOException {
final String locationString = "{\"location\":[{\"Asia\":[{\"India\":[{\"city\":\"Bengaluru\"}]}, {\"India\":[{\"city\":\"Mumbai\"}]}]}]}";
final Location location = mapper.readValue(locationString, Location.class);
System.out.println("finish");
}
}
Then all entries and levels are ok. Maybe you can have a try.
Hope this will help.
Related
I'm new to Java and writing APIs.
I basically have two things: a HashMap called db that should be returned as a JSON and an ArrayList called defaultParameters. Basically what the application does are the following:
db basically contains an array of objects of key-value pairs that should be returned as a JSON when a user makes a GET request to this address.
defaultParameters is basically a list of default key-value pairs. If there is no key-value pair within that object, then that object takes in that default key-value pair.
I was able to get it to display on the console, but for some reason, the updated values are not appearing in the JSON when I do the get request.
Here are the relevant code snippets:
private static ArrayList<Item> DB = new ArrayList<>();
private static HashMap<String, String> defaultValues = new HashMap<>();
private void updateAllItems(){
for(Item item : DB){
for(Map.Entry entry : defaultValues.entrySet()){
String currentField = (String) entry.getKey();
String currentValue = (String) entry.getValue();
item.addField(currentField, currentValue);
}
}
}
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getAllItems() {
updateAllItems();
for(Item item : DB){
// Test code that I added
item.printItem();
}
return Response.ok(DB).build();
}
Snippets of the Item class
public class Item {
private HashMap<String, String> item = new HashMap<>();
public void addField(String key, String value){
item.put(key, value);
}
public void printItem(){
for(Map.Entry entry : item.entrySet()){
String currentField = (String) entry.getKey();
String currentValue = (String) entry.getValue();
System.out.println(currentField + ": " + currentValue);
}
}
}
Doing the POST request and doing the GET request yields the following:
On the console (Something: notsomething) is new:
seller: Mrs. Fields
price: 49.99
title: Cookies
category: 42
something: notsomething
The JSON response however:
[{"category":"42","seller":"Mrs. Fields","price":"49.99","title":"Cookies"}]
The JSON is missing the new key-value pair that the console has. I'm trying to have the JSON reflect what the console is doing. Anyone have any ideas?
Alright, after some thinking, I figured out what to do.
I changed my code from
public class Item {
to
public class Item extends HashMap<String, String> {
and removed
private HashMap<String, String> item = new HashMap<>();
which means I had to change item to this. I figured since that I'm going to be using each instance as a hashmap, I may as well extend the hashmap that will change the instance of the item too.
Thanks for everyone's help. The comment gave me some more insight of what I was trying to do which led to a solution.
I have a requirement where I want to read multiple project pom files and display data in below format
{
"java" : {"1.7" : ["project1"],"1.8": ["project2"]},
"junit" : {"4.12" : ["project1"]},
"hsqldb" : {"1.8" : ["project3"],"1.1": ["project6"]}
}
My coding is getting input on project , ver and technlogy and displaying, but however I couldnt second value inside the internal
private void addTechnologyData(String projName,String techName,String ver)
{
String keyFormat=techName;
if (technologyMap.containsKey(keyFormat)) {
Map preValue=technologyMap.get(keyFormat);
if(!preValue.containsValue(projName)) {
Map<String,String> temp = new HashMap();
temp=preValue;
temp.put(ver,projName);
technologyMap.put(keyFormat, temp);
}
} else {
Map<String,String> projectVersiomap = new HashMap();
projectVersiomap.put(ver,projName);
technologyMap.put(keyFormat, projectVersiomap);
}
}
Please help me understand why I couldnt add 2nd key value pair to Internal map?
Is there a better way than what Im doing?
If my understanding is correct, you are expecting more than one project per version (since you have an array), so your Map is preventing this, you can only have one value per key. You can use a List like Map<String,Map<String, List<String>>> but I would suggest to use some POJO to keep the code cleaner.
Create a Technology class that will hold a list of Project for each version in a Map. This would look like :
public class Technology{
private String name;
private Map<String, List<Project>> projects;
public Technology(String name){
this.name = name;
this.projects = new HashMap<>();
}
public void addProjectToVersion(String version, Project project){
List<Project> l = this.projects.get(version);
if(l == null){ //doesn't exist
l = new ArrayList<>();
this.projects.put(version, l);
}
l.add(project);
}
}
Here, the logic is in the POJO. You just need to use a Collection to hold your instance, a Map if you like, or a List (implement equals and hashCode to recover easily the instance). And you can use it like :
private Map<String, Technology> techs = new HashMap<>();
....
public void addProject(String tech, String version, String projectName){
Technology t = techs.get(tech);
if(t == null){ //doesn't exist
t = new Technology(tech);
techs.put(tech, t);
}
t.addProjectToVersion(version, new Project(projectName));
}
public void insertData(){
addProject("java", "1.7", "project1");
addProject("java", "1.7", "project2");
addProject("java", "1.8", "project1");
addProject("junit", "4.12", "project1");
System.out.println(techs);
}
This will input correctly :
{junit={4.12=[project1]}, java={1.7=[project1, project2], 1.8=[project1]}}
Note that I reused your logic, based on the requirements it could be better to have a List<Project> holding each Technology with the version. But this is based on the context.
Here's the JSON:
{
"Want":{
"ABCD-1234-ABCD-1234-ABCD-125A":3,
"ABCD-1234-ABCD-1234-ABCD-129B":5,
"ABCD-1234-ABCD-1234-ABCD-123C":10
},
"EndPoint":"https://example.com/gossip/asff3"
}
And my current POJO:
public class WantMessage {
#JsonProperty("Want")
Map<String, String> wantmap = new HashMap<String, String>();
#JsonProperty("EndPoint")
public String EndPoint;
}
I get the "Endpoint" String in ok but am missing the data in the "Want" section. How would you pull in the want list?
wantmap has keys and values of type String. The map in json has int values. Try putting the numbers in double quotes, and see if it works.
In my application I have lot of overviews (tables) with sorting and filtering capabilities. And becuase the different column can hold different value type (strings, numbers, dates, sets, etc.) the filter for these columns also can bring different values. Let me show you few examples (converted to JSON already as is sent to server via REST request):
For simple string value it is like:
{"<column_name>":"<value>"}
For number and date column the filter looks like:
{"<column_name>":[{"operator":"eq","value":"<value>"}]}
{"<column_name>":[{"operator":"eq","value":"<value1>"},{"operator":"gt","value":"<value2>"}]}
For set the filter looks like
{"<column_name>":["<value1>","<value2>"(,...)]}
Now I need to parse that JSON within a helper class that will build the WHERE clause of SQL query. In PHP this is not a problem as I can call json_decode and then simply check whether some value is array, string or whatever else... But how to do this simply in Java?
So far I am using Spring's JsonJsonParser (I didn't find any visible difference between different parsers coming with Spring like Jackson, Gson and others).
I was thinking about creating an own data object class with three different constructors or having three data object classes for all of the three possibilities, but yet I have no clue how to deal with the value returned for column_name after the JSON is parsed by parser...
Simply looking on the examples it gives me three possibilities:
Map<String, String>
Map<String, Map<String, String>>
Map<String, String[]>
Any idea or clue?
Jackson's ObjectMapper treeToValue should be able to help you.
http://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/ObjectMapper.html#treeToValue%28com.fasterxml.jackson.core.TreeNode,%20java.lang.Class%29
Your main problem is that the first version of you JSON is not the same construction than the two others. Picking the two others you could deserialize your JSON into a Map<String, Map<String, String> as you said but the first version fits a Map.
There are a couple solutions available to you :
You change the JSON format to always match the Map<String, Map<String, String> pattern
You first parse the JSON into a JsonNode, check the type of the value and deserialize the whole thing into the proper Map pattern.
(quick and dirty) You don't change the JSON, but you try with one of the Map patterns, catch JsonProcessingException, then retry with the other Map pattern
You'll have to check the type of the values in runtime. You can work with a Map<String, Object> or with JsonNode.
Map<String, Object>
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, Object> map = parser.parseMap(str);
Object filterValue = filter.get("<column_name>");
if (filterValue instanceof String) {
// str is like "{\"<column_name>\":\"<value>\"}"
} else if (filterValue instanceof Collection) {
for (Object arrayValue : (Collection<Object>) filterValue) {
if (arrayValue instanceof String) {
// str is like "{\"<column_name>\":[\"<value1>\",\"<value2>\"]}"
} else if (arrayValue instanceof Map) {
// str is like "{\"<column_name>\":[{\"operator\":\"eq\",\"value\":\"<value>\"}]}"
}
}
}
JsonNode
ObjectMapper mapper = new ObjectMapper();
JsonNode filter = mapper.readTree(str);
JsonNode filterValue = filter.get("<column_name>");
if (filterValue.isTextual()) {
// str is like "{\"<column_name>\":\"<value>\"}"
} else if (filterValue.isArray()) {
for (JsonNode arrayValue : filterValue.elements()) {
if (arrayValue.isTextual()) {
// str is like "{\"<column_name>\":[\"<value1>\",\"<value2>\"]}"
} else if (arrayValue.isObject()) {
// str is like "{\"<column_name>\":[{\"operator\":\"eq\",\"value\":\"<value>\"}]}"
}
}
}
Say that I've got a
class Person {
ArrayList<MyOtherObject> lstObjects;
...
}
and then
Map<Integer, Person> personMap
and want to serialize that map with Jackson Streaming API?
JsonGenerator g =...;
g.writeArrayFieldStart("PersonMap");
if (personMap != null) {
Iterator<Map.Entry<Integer, Person>> iter = personMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Integer, Person> pairs = iter.next();
Integer key = (Integer) pairs.getKey();
Person person = (Person) pairs.getValue();
g.writeNumber(key.intValue());
person.saveToFileRaw(g); // Write the object
}
}
g.writeEndArray(); // PersonMap
and person.saveToFileRaw looks like
try {
g.writeStartObject();
g.writeObjectFieldStart("Inf");
if (lstInfo != null) {
for (PersonInfo info: lstInfo)
info.saveToFileRaw(g); // Write another object
}
g.writeEndObject();
String s = PersonType.token(type);
g.writeStringField("Tp", s);
g.writeStringField("Add", address);
So the question: how to write an array/map of custom objects? g.writeStartObject() in person.saveToFileRaw throws an exception saying that it expects a value.
Any ideas how to do this?
If you get an exception from JsonGenerator calls, you are trying to create invalid JSON structure; something that could not be parsed.
One problem I see in the code is that you first call "g.writeObjectFieldStart("Inf")", but then in loop try to call method which starts with "g.writeStartObject" -- essentially trying write start-object marker "{" twice.
You can also call "writeFieldName" separately (instead of writeObjectFieldStart()) which you probably need to do. Or maybe you need to do writeStartArray(() / writeEndArray() for PersonInfo entries; this depends on what exact output you want.