I'm using playframework, is there any way to convert a java object into Play recognizable pojo parameter list?
For example I have two class
public class A{
B b;
String room;
}
public class B{
String name;
}
When I get a A instance a, I can change it into two parameters
a.room=myroom&a.b.name=myname
Do I have to travel through a json converted from that instance?
Thanks a lot.
I wrote a Util to convert Object to playframework friendly parameter list with jackson.databind
public static List<MutablePair> getPojoParam(Object base, Object obj, boolean firstLevel) {
List<MutablePair> ret = new ArrayList<MutablePair>();
String realBase = (firstLevel) ? (String) base : ("." + base);
if (obj instanceof Collection) {
int count = 0;
for (Object o : (Collection) obj) {
List<MutablePair> subPair = getPojoParam(base, o, false);
for (MutablePair p : subPair) {
p.left = realBase + "[" + (count++) + "]" + p.left;
ret.add(p);
}
}
} else if (obj instanceof Map) {
Map<Object, Object> map = (Map<Object, Object>) obj;
for (Object k : map.keySet()) {
List<MutablePair> subPair = getPojoParam(k, map.get(k), false);
for (MutablePair p : subPair) {
p.left = realBase + p.left;
ret.add(p);
}
}
} else {
ret.add(new MutablePair(realBase, obj));
}
return ret;
}
Usage:
ATest a = new ATest();
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> data = mapper.readValue(new Gson().toJson(a), Map.class);
List<MutablePair> ret = ObjectToPOJO.getPojoParam("source", data, true);
for (MutablePair key : ret) {
System.out.println(key.left + "=" + key.right);
}
I hope it's right.
Add dependencies to use MutablePair and jackson.databind
require:
- com.fasterxml.jackson.core -> jackson-databind 2.1.0
- org.apache.commons -> commons-lang3 3.1
Related
I am writing some code to serialize a neural network system I have developed. This system has a "database" that keeps track of the evolution of the neural networks, and it does so by storing the ID of each gene in a HashMap with a GeneKey, which is a record containing the ID of the gene before and the ID of the gene after the gene we're storing.
A HashMap with some data looks like this:
existingNodes = {
GeneKey[a=0, b=3] = 4,
GeneKey[a=1, b=4] = 5
}
Everything in the system serializes fine, except this HashMap, because Json can only have numbers and strings as its keys, and in my HashMap I'm using objects for the keys. Is there an easy way to serialize this to json using Gson?
Edit: This is how the HashMap is constructed:
HashMap<GeneKey, Integer> existingNodes = new HashMap<>();
existingNodes.put(new GeneKey(0, 3), 4);
existingNodes.put(new GeneKey(1, 4), 5);
System.out.println("existingNodes = "+registry);
//existingNodes = {
// GeneKey[a=0, b=3] = 4,
// GeneKey[a=1, b=4] = 5
//}
This is the GeneKey class:
public record GeneKey(int a, int b) {}
It would be helpful if you could give the JSON string which you got after serializing using Gson.
However, please check the following code if this solves your issue.
Define GenKey class with overriden hashCode and equals methods as:
public class GeneKey {
private int a;
private int b;
public GeneKey(int a, int b) {
this.a = a;
this.b = b;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + a;
result = prime * result + b;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
GeneKey other = (GeneKey) obj;
if (a != other.a)
return false;
if (b != other.b)
return false;
return true;
}
#Override
public String toString() {
//return "GeneKey [a=" + a + ", b=" + b + "]";
// Updated for deserialization using Gson
Gson gson = new
GsonBuilder().serializeNulls().disableHtmlEscaping().create();
return gson.toJson(this);
}
}
Now try to convert the HashMap with GenKey as key into JSON String using Gson :
HashMap<GeneKey, Integer> existingNodes = new HashMap<>();
existingNodes.put(new GeneKey(0, 3), 4);
existingNodes.put(new GeneKey(1, 4), 5);
Gson gson = new
GsonBuilder().serializeNulls().disableHtmlEscaping().create();
System.out.println(gson.toJson(existingNodes));
This is the output which I received in my console:
{"{\"a\":0,\"b\":3}":4,"{\"a\":1,\"b\":4}":5}
Updating the answer to add deserialization logic if required:
//Say, we pass the serialized JSON as a payload to some REST API. We can deserialize the Key of the response as follows -
#PostMapping("/getJson")
public Map<GeneKey, Integer> getJson(#RequestBody Map<String, Integer> response) {
final Gson gson = new Gson();
Map<GeneKey, Integer> deserializedMap = new HashMap<GeneKey,
Integer>();
response.keySet().forEach( k -> {
GeneKey key = gson.fromJson(k, GeneKey.class);
deserializedMap.put(key, response.get(k));
});
System.out.println(deserializedMap);
return deserializedMap;
}
GsonBuilder#enableComplexMapKeySerialization
However, now the map is represented by an array of array of entries.
see Gson Serializing HashMap<Teacher, List<Student>>
I have two JSON strings which I want to compare.
I want neither the order of the keys to matter or the order of elements in an array.
However I do want an extra field to be considered "not equal"
Non strict mode with JSONAssert seems like it fits the bill except for an extra field being considered equal "http://jsonassert.skyscreamer.org/cookbook.html"
If at all possible I would like to avoid pulling in extra dependancies. I already have jackson in my project
I have 2 ideas how to do it.
Is to write java objects and serialize it, and write own equals method.
Is to serialize it to Map<Object, Object> and compare 2 map.
String json1 = "{...}"
String json2= "{...}"
Object json1Object = objectMapper.readValue(json1, Object.class);
Object json2Object = objectMapper.readValue(json2, Object.class);
Assertions.assertEquals(json1Object, json2Object);
Assertions.assertTrue(json1Object.equals(json2Object));
So you probably have only one option. Write own comparator.
My quick solution:
#Test
public void comparingJsonTest4() throws JsonProcessingException {
String json1 = "{\"id\": 1, \"name\": \"test\", \"cars\": [\"Ford\", \"BMW\", \"Fiat\"]}";
String json2 = "{\"name\": \"test\", \"id\": 1, \"cars\": [\"BMW\", \"Ford\", \"Fiat\"]}";
ObjectMapper objectMapper = new ObjectMapper();
JsonNode json1Node = objectMapper.readTree(json1);
JsonNode json2Node = objectMapper.readTree(json2);
Assertions.assertEquals(0, new ComparatorWithoutOrder().compare(json1Node, json2Node));
}
class ComparatorWithoutOrder implements Comparator<JsonNode> {
#Override
public int compare(JsonNode o1, JsonNode o2) {
if(o1 == o2) {
return 0;
}
if(o1.getClass() != o2.getClass()) {
return -1;
}
if(o1.getClass() == ObjectNode.class) {
List<String> o1FieldNames = new ArrayList<>();
o1.fieldNames().forEachRemaining(o1FieldNames::add);
List<String> o2FieldNames = new ArrayList<>();
o2.fieldNames().forEachRemaining(o2FieldNames::add);
if(o1FieldNames.size() != o2FieldNames.size()) {
return -1;
}
if(!o2FieldNames.containsAll(o1FieldNames) || !o1FieldNames.containsAll(o2FieldNames)) {
return -1;
}
for (String o1FieldName : o1FieldNames) {
if (!(compare(o1.get(o1FieldName), o2.get(o1FieldName)) == 0)) {
return -1;
}
}
return 0;
}
if(o1.getClass() == ArrayNode.class) {
List<JsonNode> o1Children = new ArrayList<>();
o1.elements().forEachRemaining(o1Children::add);
List<JsonNode> o2Children = new ArrayList<>();
o2.elements().forEachRemaining(o2Children::add);
if(o1Children.size() != o2Children.size()) {
return -1;
}
for (JsonNode c1 : o1Children) {
boolean found = false;
for (JsonNode c2 : o2Children) {
if (compare(c1, c2) == 0) {
found = true;
break;
}
}
if (!found) {
return -1;
}
}
return 0;
}
return o1.equals(o2) ? 0 : -1;
}
}
At the beginning I wanted to use something like this:
json1Node.equals(new ComparatorWithoutOrder(), json2Node);
but thre was a problem to propoer handle ArrayNode inside ObjectNode. So if you want, you could skip implements Comparator<JsonNode>, because finally I don't use this functionality.
I have a YML file, which I parse to Map using yamlBeans library.
I don't know how deep the nested map goes.
for example:
key1:
key2: value1
key3:
key4: value2
key5: value3
I need to find a specific value in this map, update it, and write the map back to YML file (which I know how to do).
This is my code for updating the value, and it's working.
However, this is only iterating twice through the nested map, and I need it to iterate it for as long as needed:
static void updateYmlContent(Map<String, ?> ymlMap, String value, String... keys) {
boolean found = false;
for (Map.Entry entry : ymlMap.entrySet()) {
if (entry.getKey().equals(keys[0])) {
found = true;
for (Map.Entry subEntry : ((Map<?, ?>) entry.getValue()).entrySet()) {
if (subEntry.getKey().equals(keys[1])) {
subEntry.setValue(value);
break;
} else {
throwKeyNotFoundException(keys[1]);
}
}
break;
}
}
if (!found) {
throwKeyNotFoundException(keys[0]);
}
}
Use recursion and a depth counter to drop through each level of the map.
I didn't compile this, so it probably needs a little tweaking, but here's the basic idea:
static void updateYmlContent(Map<String, ?> ymlMap, String value, String... keys) {
int depth = 0;
findAndReplaceContent(ymlMap, value, keys, depth);
}
static void findAndReplaceContent(Map map, .......) {
if (map.containsKey(keys[depth]))
{
if (depth == keys.length - 1)
{
// found it
map.put(keys[depth], value);
// done
}
else
{
findAndReplaceContent(map.get(keys[depth]), value, keys, depth+1);
}
}
else
{
// throw key not found
}
}
If ymlMap is mutable, it should be of type Map<String, Object> (ideally), i belive you have checked it already.
#SuppressWarnings("unchecked")
static void updateYmlContent(Map<String, ?> ymlMap, String value, String... keys)
{
for (int i = 0, lastIndex = keys.length - 1; i <= lastIndex; i++)
{
String key = keys[i];
Object v = ymlMap.get(key);
if (v == null) // Assumed value is never null, if key exists
throw new /* KeyNotFound */ RuntimeException("Key '" + key + "' not found");
if (i < lastIndex)
ymlMap = (Map<String, Object>) v;
else
((Map<String, String>) ymlMap).put(key, value);
}
}
You can do it via one for loop, please see the example:
private static void updateYmlContent(Map<String, Object> map, String newValue, String... keys) {
for (int i = 0; i < keys.length; i++) {
if (i + 1 == keys.length) {
map.put(keys[i], newValue);
return;
}
if (map.get(keys[i]) instanceof Map) {
map = (Map<String, Object>) map.get(keys[i]);
} else {
throw new RuntimeException();
}
}
throw new RuntimeException();
}
Also please see how it is used:
public static void main(String[] keys) throws Exception {
Map<String, Object> ymlMap = new HashMap<>();
Map<Object, Object> nested1 = new HashMap<>();
Map<Object, Object> nested2 = new HashMap<>();
nested2.put("key3", "oldvalue1");
nested2.put("key4", "oldvalue2");
nested1.put("key2", nested2);
ymlMap.put("key1", nested1);
updateYmlContent(ymlMap, "new", "key1", "key2", "key3");
}
I have a JSONObject
{"2016":{"12":{"20":{"19":{"DonationTime":11111111111,"Donation":10}}}}}
I want to convert it to new map with each keys
int i = 0;
for (Iterator<String> keysItr = object.keySet().iterator(); keysItr.`hasNext(); i++) {
String key = keysItr.next();
Object value = object.get(key);
if(value instanceof JSONObject) {
value = toMap((JSONObject) value);
map.put(key, value);
}
}
SOP(map); //but here i want to get 4 maps
}
I want to get 4 maps like
hourMap[19] = "{"DonationTime":11111111111,"Donation":10}";
dayMap[20] = "{"19":{"DonationTime":11111111111,"Donation":10}}";
monthMap[12] = "{"12":{"20":{"19":{"DonationTime":11111111111,"Donation":10}}}";
yearMap[2016] = "{"12":{"20":{"19":{"DonationTime":11111111111,"Donation":10}}}";
I am using for loop yet i am unable to get incremented value for i.
Well u can simply convert the JSON object into a map and then from there u can easily take out the four maps u interested in
here is a simple example
(watch out the code below on a big JSON graph can cause u some problems since it is a recursion based conversion)
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.*;
public class JsonMapConverter {
public static void main(String... x) throws Exception {
String jsonString = "{\"2016\":{\"12\":{\"20\":{\"19\":{\"DonationTime\":11111111111,\"Donation\":10}}}}}";
JSONObject json = new JSONObject(jsonString);
Map<String,Object> yearMap = toMap(json);
String year = yearMap.keySet().iterator().next();
Map<String,Object> monthMap = ((Map<String, Object>) yearMap.get(year));
String month = monthMap.keySet().iterator().next();
Map<String,Object> dayMap = (Map<String, Object>) monthMap.get(month);
String day = dayMap.keySet().iterator().next();
Map<String,Object> hourMap = (Map<String, Object>) dayMap.get(day);
System.out.println(yearMap);
System.out.println(monthMap);
System.out.println(dayMap);
System.out.println(hourMap);
}
public static Map<String, Object> toMap(JSONObject object) throws JSONException {
Map<String, Object> map = new HashMap<String, Object>();
Iterator<String> keysItr = object.keys();
while(keysItr.hasNext()) {
String key = keysItr.next();
Object value = object.get(key);
if(value instanceof JSONArray) {
value = toList((JSONArray) value);
}
else if(value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
map.put(key, value);
}
return map;
}
public static List<Object> toList(JSONArray array) throws JSONException {
List<Object> list = new ArrayList<Object>();
for(int i = 0; i < array.length(); i++) {
Object value = array.get(i);
if(value instanceof JSONArray) {
value = toList((JSONArray) value);
}
else if(value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
list.add(value);
}
return list;
}
}
for JSON to map conversion i use the code from this answer (Convert a JSON String to a HashMap)
the code was written based on the json string, u may adjust the code according to your needs in case of multiple years,month and days presents in the json
I want to convert HashMap to json array my code is as follow:
Map<String, String> map = new HashMap<String, String>();
map.put("first", "First Value");
map.put("second", "Second Value");
I have tried this but it didn't work. Any solution?
JSONArray mJSONArray = new JSONArray(Arrays.asList(map));
Try this,
public JSONObject (Map copyFrom)
Creates a new JSONObject by copying all name/value mappings from the given map.
Parameters
copyFrom a map whose keys are of type String and whose values are of supported types.
Throws
NullPointerException if any of the map's keys are null.
Basic usage:
JSONObject obj=new JSONObject(yourmap);
get the json array from the JSONObject
Edit:
JSONArray array=new JSONArray(obj.toString());
Edit:(If found Exception then You can change as mention in comment by #krb686)
JSONArray array=new JSONArray("["+obj.toString()+"]");
Since androiad API Lvl 19, you can simply do new JSONObject(new HashMap()). But on older API lvls you get ugly result(simple apply toString to each non-primitive value).
I collected methods from JSONObject and JSONArray for simplify and beautifully result. You can use my solution class:
package you.package.name;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Map;
public class JsonUtils
{
public static JSONObject mapToJson(Map<?, ?> data)
{
JSONObject object = new JSONObject();
for (Map.Entry<?, ?> entry : data.entrySet())
{
/*
* Deviate from the original by checking that keys are non-null and
* of the proper type. (We still defer validating the values).
*/
String key = (String) entry.getKey();
if (key == null)
{
throw new NullPointerException("key == null");
}
try
{
object.put(key, wrap(entry.getValue()));
}
catch (JSONException e)
{
e.printStackTrace();
}
}
return object;
}
public static JSONArray collectionToJson(Collection data)
{
JSONArray jsonArray = new JSONArray();
if (data != null)
{
for (Object aData : data)
{
jsonArray.put(wrap(aData));
}
}
return jsonArray;
}
public static JSONArray arrayToJson(Object data) throws JSONException
{
if (!data.getClass().isArray())
{
throw new JSONException("Not a primitive data: " + data.getClass());
}
final int length = Array.getLength(data);
JSONArray jsonArray = new JSONArray();
for (int i = 0; i < length; ++i)
{
jsonArray.put(wrap(Array.get(data, i)));
}
return jsonArray;
}
private static Object wrap(Object o)
{
if (o == null)
{
return null;
}
if (o instanceof JSONArray || o instanceof JSONObject)
{
return o;
}
try
{
if (o instanceof Collection)
{
return collectionToJson((Collection) o);
}
else if (o.getClass().isArray())
{
return arrayToJson(o);
}
if (o instanceof Map)
{
return mapToJson((Map) o);
}
if (o instanceof Boolean ||
o instanceof Byte ||
o instanceof Character ||
o instanceof Double ||
o instanceof Float ||
o instanceof Integer ||
o instanceof Long ||
o instanceof Short ||
o instanceof String)
{
return o;
}
if (o.getClass().getPackage().getName().startsWith("java."))
{
return o.toString();
}
}
catch (Exception ignored)
{
}
return null;
}
}
Then if you apply mapToJson() method to your Map, you can get result like this:
{
"int": 1,
"Integer": 2,
"String": "a",
"int[]": [1,2,3],
"Integer[]": [4, 5, 6],
"String[]": ["a","b","c"],
"Collection": [1,2,"a"],
"Map": {
"b": "B",
"c": "C",
"a": "A"
}
}
A map consists of key / value pairs, i.e. two objects for each entry, whereas a list only has a single object for each entry. What you can do is to extract all Map.Entry<K,V> and then put them in the array:
Set<Map.Entry<String, String> entries = map.entrySet();
JSONArray mJSONArray = new JSONArray(entries);
Alternatively, sometimes it is useful to extract the keys or the values to a collection:
Set<String> keys = map.keySet();
JSONArray mJSONArray = new JSONArray(keys);
or
List<String> values = map.values();
JSONArray mJSONArray = new JSONArray(values);
Note: If you choose to use the keys as entries, the order is not guaranteed (the keySet() method returns a Set). That is because the Map interface does not specify any order (unless the Map happens to be a SortedMap).
This is the Simplest Method.
Just use
JSONArray jarray = new JSONArray(hashmapobject.toString);
You can use
JSONArray jarray = JSONArray.fromObject(map );