OrientDB serialize a query result directly to Json - java

I have a orient db query and want to pass the result back as json. So I have the following code:
OSQLSynchQuery<ODocument> q = new OSQLSynchQuery<ODocument>(query);
List<ODocument> result = db.command(q).execute();
return new ObjectMapper().writeValueAsString(result);
But since the serialization of ODocument leads to an infinite recursion (see this SO question) I have a problem.
Looping the list and concatinating ODocument#toJSON() to a StringBuffer is not my prefered option. Especially since I also have a case where I transform a "group by" result into a hashmap which I want to be json too. So is there a clean way on json serializing ODcomument as part of another object (list or map)?

This is for sure not a very nice way but this is working quite well. First I loop through the result and build a list of json strings:
List<String> jsonResult = new ArrayList<>();
for (ODocument d : queryResult) jsonResult.add(d.toJSON());
Next I have made a custom serializer which is just writing the raw string (json) to the buffer:
public class RawJsonListJacksonSerializer extends JsonSerializer<List<String>> {
#Override
public void serialize(List<String> entries, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartArray();
for (int i=0; i<entries.size(); i++) {
jsonGenerator.writeRaw(entries.get(i) == null ? "null" : entries.get(i));
if (i<entries.size()-1) jsonGenerator.writeRaw(",");
}
jsonGenerator.writeEndArray();
}
}

Related

Can somebody explain how SharedPreferences stores a string set

I was trying to save a list of names in shared preferences and wanted to make use of the SharedPreferences putStringSet so that I won't need multiple key-value pairs.
I know a regular HashSet doesn't guarantee iteration order so I used a LinkedHashSet to maintain the iteration order as the order of insertion and saved that to shared preferences.
When retrieving the same String Set I also used a LinkedHashSet but the order was not the same as when originally inserted.
I solved the problem by just storing the names in a comma separated string then parsing that, so that's not my question.
I would like to know what SharedPreferences does to a set of strings so that it does not maintain the correct ordering (at least not the one I want)?
To answer your question in short it un-links your linked hash set by creating a simple HashSet from your values... from that point on - no links of order.
From source code:
public Editor putStringSet(String key, Set<String> values) {
synchronized (this) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
return this;
}
}
Thats why, like you did, if you want to preserve order you need to store as string with separators (you used coma) and then split the separator to get an array.
There is one trick I use often is JSON.
It is very easy to convert complex objects to JSON and that save them as simple string into SharedPreferences.
You just need few lines of code to do this.
Have a look at popular JSON libraries like GSON and Jackson
Here is an util class you can use to convert object into JSON and vice versa.
public class JsonUtils {
private JsonUtils() {
}
public static String convertObjectToJSONString(Object object) {
ObjectMapper objectMapper = new ObjectMapper();
String jsonObject = "";
try {
jsonObject = objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return jsonObject;
}
public static<T> T parseObjectFromString(String json,Class<T> objectClass) {
ObjectMapper objectMapper = new ObjectMapper();
T object = null;
try {
object = objectMapper.readValue(json, objectClass);
} catch (IOException e) {
e.printStackTrace();
}
return object;
}
}
If you still want to save String Set into shared preference this is on thing you should not miss is to remove old set and than insert new one.
Set<String> stringSet = sharedPrefs.getStringSet("set-key", new HashSet<String>());
stringSet.add("Hello World");
Editor edit = sharedPrefs.edit();
edit.remove("set-key");
edit.apply();
edit.putStringSet("set-key", stringSet);
Also you can just use plain old ObjectOutputStream to convert object into byte[] or String and than save it. But as a lot of people noticed this is out of the scope of this question.

Parsing dynamic JSON values to Java objects

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>\"}]}"
}
}
}

Jackson how to transform JsonNode to ArrayNode without casting?

I am changing my JSON library from org.json to Jackson and I want to migrate the following code:
JSONObject datasets = readJSON(new URL(DATASETS));
JSONArray datasetArray = datasets.getJSONArray("datasets");
Now in Jackson I have the following:
ObjectMapper m = new ObjectMapper();
JsonNode datasets = m.readTree(new URL(DATASETS));
ArrayNode datasetArray = (ArrayNode)datasets.get("datasets");
However I don't like the cast there, is there the possibility for a ClassCastException?
Is there a method equivalent to getJSONArray in org.json so that I have proper error handling in case it isn't an array?
Yes, the Jackson manual parser design is quite different from other libraries. In particular, you will notice that JsonNode has most of the functions that you would typically associate with array nodes from other API's. As such, you do not need to cast to an ArrayNode to use. Here's an example:
JSON:
{
"objects" : ["One", "Two", "Three"]
}
Code:
final String json = "{\"objects\" : [\"One\", \"Two\", \"Three\"]}";
final JsonNode arrNode = new ObjectMapper().readTree(json).get("objects");
if (arrNode.isArray()) {
for (final JsonNode objNode : arrNode) {
System.out.println(objNode);
}
}
Output:
"One"
"Two"
"Three"
Note the use of isArray to verify that the node is actually an array before iterating. The check is not necessary if you are absolutely confident in your datas structure, but its available should you need it (and this is no different from most other JSON libraries).
In Java 8 you can do it like this:
import java.util.*;
import java.util.stream.*;
List<JsonNode> datasets = StreamSupport
.stream(obj.get("datasets").spliterator(), false)
.collect(Collectors.toList())
I would assume at the end of the day you want to consume the data in the ArrayNode by iterating it. For that:
Iterator<JsonNode> iterator = datasets.withArray("datasets").elements();
while (iterator.hasNext())
System.out.print(iterator.next().toString() + " ");
or if you're into streams and lambda functions:
import com.google.common.collect.Streams;
Streams.stream(datasets.withArray("datasets").elements())
.forEach( item -> System.out.print(item.toString()) )
Is there a method equivalent to getJSONArray in org.json so that I have proper error handling in case it isn't an array?
It depends on your input; i.e. the stuff you fetch from the URL. If the value of the "datasets" attribute is an associative array rather than a plain array, you will get a ClassCastException.
But then again, the correctness of your old version also depends on the input. In the situation where your new version throws a ClassCastException, the old version will throw JSONException. Reference: http://www.json.org/javadoc/org/json/JSONObject.html#getJSONArray(java.lang.String)
Obtain an iterator by calling the JsonNode's iterator() method, and go on...
JsonNode array = datasets.get("datasets");
if (array.isArray()) {
Iterator<JsonNode> itr = array.iterator();
/* Set up a loop that makes a call to hasNext().
Have the loop iterate as long as hasNext() returns true.*/
while (itr.hasNext()) {
JsonNode item=itr.next();
// do something with array elements
}
}

Can't parse JSON array of arrays to LinkedHashMap in Jackson

I' m developing an Android REST client. We use JSON as data exchange format, so I use a Jackson parser. I get different Json responses from the server like simple arrays:
{"user_id":"332","user_role":"1"}
or something else. All these stuff I parse to LinkedHashMap<String, Object> and everything works perfectly but when I got this response from the server:
[ { "user_id":"352",
"user_role":"expert",
"name":"Test 12-18",
"description":"Test" },
{ "user_id":"263",
"user_role":"novice lab",
"name":"Tom's Desk",
"description":"Desk"}
]
I got null: {} after parsing.Here is my code where i use Jackson:
ObjectMapper mapParametersToJSON = new ObjectMapper();
String serverResponseBody = responseFromServer.getBody();
LinkedHashMap<String, Object> resultofOperation = new LinkedHashMap<String,
Object>();
TypeReference<LinkedHashMap<String,Object>> genericTypeReferenceInformation = new
TypeReference<LinkedHashMap<String,Object>>() {};
try {
resultofOperation = mapParametersToJSON.readValue(serverResponseBody,
genericTypeReferenceInformation);
So, why Jackson failed to parse this? How can I fix this?
Others have suggested the problem, but solutions are bit incomplete. If you need to deal with JSON Objects and Arrays, you can either bind to java.lang.Object, check the type:
Object stuff = objectMapper.readValue(json, Object.class);
and you will get either List or Map (specifically, ArrayList or LinkedHashMap, by default; these defaults can be changed).
Or you can do JSON trees with JsonNode:
JsonNode root = objectMapper.readTree(json);
if (root.isObject()) { // JSON Object
} else if (root.isArray()) { ...
}
latter is often more convenient.
One nice thing is that you can still create regular POJOs out of these, for example:
if (root.isObject()) {
MyObject ob = objectMapper.treeToValue(MyObject.class);
}
// or with Object, use objectMapper.convertValue(ob, MyObject.class)
so you can even have different handling for different types; go back and forth different representations.
The first JSON in your question is a map, or an object. The second is an array. You're not parsing an array, you're parsing a map.
You need to do something like this:
List<MyClass> myObjects = mapper.readValue(jsonInput, new TypeReference<List<MyClass>>(){});
Almost identical question with answer here.
In JSON the {"key": "value"} is Object and the ["this", "that"] is Array.
So, in case when you're receiving the array of objects you should use something like List<Map<Key, Value>>.
You are facing an error, because [] construction can't be translated into Map reference, only in List or array.
I would recommend do it something in this way:
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String,String>> parsedResult = objectMapper.reader(CollectionType.construct(LinkedList.class, MapType.construct(LinkedHashMap.class, SimpleType.construct(String.class), SimpleType.construct(String.class)))).readValue(serverResponseBody);
//if you need the one result map
Map<String, String> resultMap = new LinkedHashMap<String, String>();
for (Map<String, String> map: parsedResult){
resultMap.putAll(map);
}

How to serialize a map<Integer, Custom object> with Jackson streaming API?

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.

Categories