I would like to know a method for flagging values in an array, removing the duplicates and combining some of the data in Java.
I am keeping a record of geo locations using lat, long and description this is encoded in a JSON array as follows:
[{"lon": 0.001, "lat": 0.001, "desc": test}, {"lon": 0.001, "lat": 0.001, "desc": test2}]
I would like to be able to remove the duplicate geo locations while keeping the "desc" part of the array, e.g.
[{"lon": 0.001, "lat": 0.001, "desc": test, test2}]
Edit:
This is what I am currently doing:
//Store locPoints from server in JSONArray
JSONArray jPointsArray = new JSONArray(serverData);
List<JSONObject> jObjects = new ArrayList<JSONObject>();
List<JSONObject> seenObjects = new ArrayList<JSONObject>();
for(int i = 0; i < jPointsArray.length(); ++i)
{
jObjects.add(jPointsArray.getJSONObject(i));
}
for (JSONObject obj : jObjects)
{
//This always returns true
if (!seenObjects.contains(obj))// && !seenObjects.contains(obj.get("lon")))
{
Log.i("Sucess", "Huzzah!");
seenObjects.add(obj);
}
else
{
//merge the 'desc' field in 'obj' with the 'desc' field in
JSONObject original = (JSONObject)seenObjects.get(seenObjects.indexOf(obj));
JSONObject update = obj;
original.put("desc", original.get("desc") + ", " + update.get("desc"));
seenObjects.get(seenObjects.indexOf(obj)).get("desc"));
}
}
You could do something like:
//assuming that the array you are filtering is called 'myArray'
List<Object> seenObjects = new ArrayList<Object>();
for (Object obj : myArray) {
if (! seenObjects.contains(obj)) {
seenObjects.add(obj);
}
else {
//merge the 'desc' field in 'obj' with the 'desc' field in
//'seenObjects.get(seenObjects.indexOf(obj))'
}
}
Note that this will only work if the objects you are comparing have implementations of equals() and hashCode() that do what you want (in your case, they should only take into consideration the 'lat' and 'lon' fields).
Update:
Here is some complete example code:
import java.util.ArrayList;
import java.util.List;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
public class JsonMergeTest {
#SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) {
List<Object> myArray = new ArrayList<Object>();
myArray.add(MyJsonObject.parse("{\"lon\": 0.001, \"lat\": 0.001, \"desc\": \"test\"}"));
myArray.add(MyJsonObject.parse("{\"lon\": 0.001, \"lat\": 0.001, \"desc\": \"test2\"}"));
List seenObjects = new ArrayList<Object>();
for (Object obj : myArray) {
if (! seenObjects.contains(obj)) {
seenObjects.add(obj);
}
else {
//merge the 'desc' field in 'obj' with the 'desc' field in the list
MyJsonObject original = (MyJsonObject)seenObjects.get(seenObjects.indexOf(obj));
MyJsonObject update = (MyJsonObject)obj;
original.put("desc", original.get("desc") + ", " + update.get("desc"));
}
}
for (MyJsonObject obj : (List<MyJsonObject>)seenObjects) {
System.out.println(obj.toJSONString());
}
}
private static class MyJsonObject extends JSONObject {
#Override
public boolean equals(Object obj) {
if (obj == null || ! (obj instanceof MyJsonObject) || ! this.containsKey("lat") || ! this.containsKey("lon")) {
return super.equals(obj);
}
MyJsonObject jsonObj = (MyJsonObject)obj;
return this.get("lat").equals(jsonObj.get("lat")) && this.get("lon").equals(jsonObj.get("lon"));
}
#Override
public int hashCode() {
if (! this.containsKey("lat") || ! this.containsKey("lon")) {
return super.hashCode();
}
return this.get("lat").hashCode() ^ this.get("lon").hashCode();
}
#SuppressWarnings("unchecked")
public static Object parse(String json) {
Object parsedJson = JSONValue.parse(json);
if (! (parsedJson instanceof JSONObject)) {
return parsedJson;
}
MyJsonObject result = new MyJsonObject();
result.putAll((JSONObject)parsedJson);
return result;
}
}
}
You can use GSon. And follow the steps:
1. Define an equivalent POJO in Java, to map the JSON String
public class Location implements Comparable<Location> {
public String lon;
public String lat;
public String desc;
#Override
public String toString() {
return "<lon: " + lon +", lat: "+ lat +", desc: " + desc +">";
}
#Override
public boolean equals(Object obj) {
return ((Location)obj).lon.equals(lon) && ((Location)obj).lat.equals(lat);
}
public int compareTo(Location obj) {
return ((Location)obj).lon.compareTo(lon) + ((Location)obj).lat.compareTo(lat);
}
}
2. Write the code that merges similar location. OK, it's Sunday, lets do it :)
public static void main(String[] args){
//Some test data
String s = "[" +
" {\"lon\": 0.001, \"lat\": 0.001, \"desc\": \"test\"}," +
" {\"lon\": 0.002, \"lat\": 0.001, \"desc\": \"test3\"}," +
" {\"lon\": 0.002, \"lat\": 0.005, \"desc\": \"test4\"}," +
" {\"lon\": 0.002, \"lat\": 0.001, \"desc\": \"test5\"}," +
" {\"lon\": 0.001, \"lat\": 0.001, \"desc\": \"test2\"}]";
Gson gson = new Gson();
Location[] al = gson.fromJson(s, Location[].class);
List<Location> tl = Arrays.asList(al);
//lets sort so that similar locations are grouped
Collections.sort(tl);
List<Location> fl = new ArrayList<Location>();
Location current = null;
//merge!
for(Iterator<Location> it = tl.iterator(); it.hasNext();){
current = current==null?it.next():current;
Location ltmp = null;
while(it.hasNext() && (ltmp = it.next()).equals(current))
current.desc = current.desc + "," + ltmp.desc;
fl.add(current);
current = ltmp;
}
//convert back to JSON?
System.out.println(gson.toJson(fl));
}
3. output
[{"lon":"0.002","lat":"0.005","desc":"test4"},
{"lon":"0.002","lat":"0.001","desc":"test3,test5"},
{"lon":"0.001","lat":"0.001","desc":"test,test2"}]
Related
Replacing a value in a javax.json.JsonObject ist not possible directly because javax.json.JsonObject implements an immutable map. In order to do that you have to create a new JsonObject and copy the values from the original one into the new one taking care of replacing the value you want to replace.
I found examples of how to do that with "simple" JsonObject, where there are no nested JsonObjects. What I'm looking for is a general replace implementation where I pass a JsonObject, the attribute name and the new value. This method should "traverse" the JsonObject and replace the attribute (wherever in the object hierarchy it is) and leave the others attributes unchanged.
For ex. this is my original JsonObject
{
"Attr1":number1,
"Attr2":number2,
"Attr3":number3,
"Attr4":[
"string1"
],
"Attr5":[
{
"Attr6":[
{
"Attr7":"string2",
"Attr8":"string3",
"$Attr9":number4
},
{
"Attr7":"string4",
"Attr8":"string5",
"Attr9":number5
}
],
"Attr10":number6,
"Attr14":{
"Attr10":"string6",
"Attr11":"string7",
"Attr12":"string8"
},
"Attr13":[
"string9",
"string10"
],
"Attr14":"string11"
}
]
}
and I want to replace the Attr6 with just an array of strings instead of an array of JsonObjects:
"Attr6":["newString1","newString2"],
The corresponding call could be something like replaceValue(JsonObject jObj, String attrName, JsonValue newValue)) where 'jObj' is the entire Json, 'attrName' is 'Attr6' and 'newValue' is a JsonArray containing the two strings.
Can someone point me to an example where such a feature is implemented or help me with it?
I tried by myself with this, but it doesn't really work because the builder is re-created on every recursive iteration (or just more probably because it is all wrong... :) )
public static JsonObject replaceValue( final JsonObject jsonObject, final String jsonKey, final JsonValue jsonValue )
{
JsonObjectBuilder builder = Json.createObjectBuilder();
if(jsonObject == null)
{
return builder.build();
}
Iterator<Entry<String, JsonValue>> it = jsonObject.entrySet().iterator();
while (it.hasNext())
{
#SuppressWarnings( "rawtypes" )
JsonObject.Entry mapEntry = it.next();
if (mapEntry.getKey() == jsonKey)
{
builder.add(jsonKey, jsonValue);
}
else if (ValueType.STRING.equals(((JsonValue) mapEntry.getValue()).getValueType()) || ValueType.NUMBER.equals(((JsonValue) mapEntry.getValue()).getValueType()) || ValueType.TRUE.equals(((JsonValue) mapEntry.getValue()).getValueType()) ||
ValueType.FALSE.equals(((JsonValue) mapEntry.getValue()).getValueType()) || (JsonValue) mapEntry.getValue() == null || "schemas".equalsIgnoreCase((String) mapEntry.getKey()))
{
builder.add(mapEntry.getKey().toString(), (JsonValue) mapEntry.getValue());
}
else if (ValueType.OBJECT.equals(((JsonValue) mapEntry.getValue()).getValueType()))
{
JsonObject modifiedJsonobject = (JsonObject) mapEntry.getValue();
if (modifiedJsonobject != null)
{
replaceValue(modifiedJsonobject, jsonKey, jsonValue);
}
}
else if (ValueType.ARRAY.equals(((JsonValue) mapEntry.getValue()).getValueType()))
{
for (int i = 0; i < ((JsonValue) mapEntry.getValue()).asJsonArray().size(); i++)
{
replaceValue((JsonObject) ((JsonValue) mapEntry.getValue()).asJsonArray().get(i), jsonKey, jsonValue);
}
}
}
return builder.build();
}
This is an alternative approach to solving the problem, one which uses the streaming parser provide by javax.json.stream.JsonParser. This generates a stream of tokens from the JSON source, with javax.json.stream.JsonParser.Event values which describe the type of token (e.g. START_OBJECT, KEY_NAME, and so on).
Most importantly for us, there are skipObject() and skipArray() methods on the parser, which allow us to cut out the unwanted section of our source JSON.
The overall approach is to build a new version of the JSON, token-by-token, as a string, substituting the replacement section when we reach the relevant location (or multiple locations) in the JSON.
Finally, we convert the new string back to an object, so we can pretty-print it.
There is no recursion used in this approach.
import java.io.IOException;
import javax.json.Json;
import javax.json.stream.JsonParser;
import javax.json.stream.JsonParser.Event;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import javax.json.JsonObject;
import javax.json.JsonWriterFactory;
import javax.json.stream.JsonGenerator;
public class StreamDemo {
public static void doStream() throws IOException {
JsonParser jsonParser = Json.createParser(new StringReader(JSONSTRING));
StringBuilder sb = new StringBuilder();
Event previous = null;
String targetKeyName = "Attr6";
String replacement = "[\"newString1\",\"newString2\"]";
// This event reflects the end of the "replacement" string - namely "]".
// We need this because this event may be different from the replaced event.
Event replacementPreviousEvent = Event.END_ARRAY;
// Used when we find the target key for replacement:
boolean doReplacement = false;
while (jsonParser.hasNext()) {
Event event = jsonParser.next();
if (doReplacement) {
// Skip over the structure we want to replace:
if (event.equals(Event.START_OBJECT)) {
jsonParser.skipObject();
} else if (event.equals(Event.START_ARRAY)) {
jsonParser.skipArray();
}
// Write the replacement fragment here:
sb.append(replacement);
// Move to the next event in the stream:
event = jsonParser.next();
previous = replacementPreviousEvent;
doReplacement = false;
}
if (Event.KEY_NAME.equals(event)
&& jsonParser.getString().equals(targetKeyName)) {
doReplacement = true;
}
switch (event) {
case START_OBJECT:
if (Event.END_OBJECT.equals(previous)) {
sb.append(",");
}
sb.append("{");
break;
case END_OBJECT:
sb.append("}");
break;
case START_ARRAY:
sb.append("[");
break;
case END_ARRAY:
sb.append("]");
break;
case KEY_NAME:
sb = previousWasAValue(previous, sb);
sb = previousWasAnEnd(previous, sb);
sb.append("\"").append(jsonParser.getString()).append("\":");
break;
case VALUE_STRING:
sb = previousWasAValue(previous, sb);
sb.append("\"").append(jsonParser.getString()).append("\"");
break;
case VALUE_NUMBER:
sb = previousWasAValue(previous, sb);
if (jsonParser.isIntegralNumber()) {
sb.append(jsonParser.getLong());
} else {
sb.append(jsonParser.getBigDecimal().toPlainString());
}
break;
case VALUE_TRUE:
sb = previousWasAValue(previous, sb);
sb.append("true");
break;
case VALUE_FALSE:
sb = previousWasAValue(previous, sb);
sb.append("false");
break;
case VALUE_NULL:
sb = previousWasAValue(previous, sb);
sb.append("null");
break;
default:
break;
}
previous = event;
}
// At the end, pretty-print the new JSON:
JsonObject modifiedObject = Json.createReader(new StringReader(sb.toString())).readObject();
Map<String, Boolean> config = new HashMap<>();
config.put(JsonGenerator.PRETTY_PRINTING, true);
String jsonString;
JsonWriterFactory writerFactory = Json.createWriterFactory(config);
try ( Writer writer = new StringWriter()) {
writerFactory.createWriter(writer).write(modifiedObject);
jsonString = writer.toString();
}
System.out.println(jsonString);
}
private static StringBuilder previousWasAValue(Event previous, StringBuilder sb) {
// The current value follows another value - so a separating comma is needed:
if (Event.VALUE_STRING.equals(previous)
|| Event.VALUE_NUMBER.equals(previous)
|| Event.VALUE_TRUE.equals(previous)
|| Event.VALUE_FALSE.equals(previous)
|| Event.VALUE_NULL.equals(previous)) {
sb.append(",");
}
return sb;
}
private static StringBuilder previousWasAnEnd(Event previous, StringBuilder sb) {
// The current key follows the end of an object or an array, so a
// separating comma is needed:
if (Event.END_OBJECT.equals(previous)
|| Event.END_ARRAY.equals(previous)) {
sb.append(",");
}
return sb;
}
private static final String JSONSTRING
= """
{
"Attr0": null,
"Attr1": true,
"Attr2": false,
"Attr3": 3,
"Attr4": [
"string1"
],
"Attr5": [{
"Attr6": [{
"Attr7": "string2",
"Attr8": "string3",
"Attr9": 4
},
{
"Attr7": "string4",
"Attr8": "string5",
"Attr9": 5
}
],
"Attr10": 6,
"Attr14": {
"Attr10": "string6",
"Attr11": "string7",
"Attr12": "string8"
},
"Attr13": [
"string9",
123.45,
false
],
"Attr15": "string11"
}]
}
""";
}
After having taken a cue from Kolban's answer in this post Convert a JSON String to a HashMap I should have found a solution:
public class JsonUtils
{
public static JsonObject replaceValue( final JsonObject jsonObject, final String jsonKey, final Object jsonValue )
{
JsonObjectBuilder builder = Json.createObjectBuilder();
if (jsonObject != JsonObject.NULL)
{
builder = replace(jsonObject, jsonKey, jsonValue, builder);
}
return builder.build();
}
private static JsonObjectBuilder replace( final JsonObject jsonObject, final String jsonKey, final Object jsonValue, final JsonObjectBuilder builder )
{
Iterator<Entry<String, JsonValue>> it = jsonObject.entrySet().iterator();
while (it.hasNext())
{
#SuppressWarnings( "rawtypes" )
JsonObject.Entry mapEntry = it.next();
String key = mapEntry.getKey().toString();
Object value = mapEntry.getValue();
if (key.equalsIgnoreCase(jsonKey))
{
if (jsonValue instanceof String)
{
builder.add(jsonKey, (String) jsonValue);
}
else
{
builder.add(jsonKey, (JsonValue) jsonValue);
}
// here you can add the missing casting you need
continue;
}
if (value instanceof JsonArray)
{
value = toJsonArray((JsonArray) value, jsonKey, jsonValue, builder);
}
else if (value instanceof JsonObject)
{
JsonObjectBuilder newBuilder = Json.createObjectBuilder();
value = replace((JsonObject) value, jsonKey, jsonValue, newBuilder);
if (value instanceof JsonObjectBuilder)
{
value = ((JsonObjectBuilder) value).build();
}
}
builder.add(key, (JsonValue) value);
}
return builder;
}
private static JsonArray toJsonArray( final JsonArray array, final String jsonKey, final Object jsonValue, final JsonObjectBuilder builder )
{
JsonArrayBuilder jArray = Json.createArrayBuilder();
for (int i = 0; i < array.size(); i++)
{
Object value = array.get(i);
if (value instanceof JsonArray)
{
value = toJsonArray((JsonArray) value, jsonKey, jsonValue, builder);
}
else if (value instanceof JsonObject)
{
JsonObjectBuilder newBuilder = Json.createObjectBuilder();
value = replace((JsonObject) value, jsonKey, jsonValue, newBuilder);
if (value instanceof JsonObjectBuilder)
{
value = ((JsonObjectBuilder) value).build();
}
}
jArray.add((JsonValue) value);
}
return jArray.build();
}
Just keep in mind that this works if the key you want to replace is unique in the whole JsonObject.
Any improvement is more than appreciated...
If you don't want to use the streaming API (as used in my other answer), I think you can achieve a more compact approach - which is similar to yours - using JsonObjectBuilder and JsonOArrayBuilder, together with recursion:
private static JsonStructure iterate(final JsonStructure json) {
if (json.getValueType().equals(ValueType.OBJECT)) {
JsonObjectBuilder builder = Json.createObjectBuilder();
json.asJsonObject().forEach((key, value) -> {
switch (value.getValueType()) {
case OBJECT:
if (key.equals(targetKey)) {
builder.add(key, replacementJson);
} else {
builder.add(key, iterate(value.asJsonObject()));
} break;
case ARRAY:
if (key.equals(targetKey)) {
builder.add(key, replacementJson);
} else {
builder.add(key, iterate(value.asJsonArray()));
} break;
default:
if (key.equals(targetKey)) {
builder.add(key, replacementJson);
} else {
builder.add(key, value);
} break;
}
});
return builder.build();
} else if (json.getValueType().equals(ValueType.ARRAY)) {
JsonArrayBuilder builder = Json.createArrayBuilder();
json.asJsonArray().forEach((value) -> {
switch (value.getValueType()) {
case OBJECT:
builder.add(iterate(value.asJsonObject()));
break;
case ARRAY:
builder.add(iterate(value.asJsonArray()));
break;
default:
builder.add(value);
break;
}
});
return builder.build();
}
return null;
}
Personally, it's harder for me to read this recursive code than it is for me to read the streaming code in my other answer. But it certainly more concise.
It works by iterating down into the nested levels of each JSON object and array, and then builds a copy of the original data from the deepest nested levels outwards. When it finds the specified replacement key, it uses the related replacement JSON as the key's value.
The above method can be invoked as follows - which pretty-prints the end result:
final JsonStructure jsonOriginal = Json.createReader(new StringReader(JSONSTRING)).readObject();
final JsonStructure jsonCopy = iterate(jsonOriginal);
Map<String, Boolean> config = new HashMap<>();
config.put(JsonGenerator.PRETTY_PRINTING, true);
String jsonString;
JsonWriterFactory writerFactory = Json.createWriterFactory(config);
try ( Writer writer = new StringWriter()) {
writerFactory.createWriter(writer).write(jsonCopy);
jsonString = writer.toString();
}
System.out.println(jsonString);
For my replacement JSON I used this, showing some test data examples:
private final static String targetKey = "Attr6";
//private final static JsonStructure replacementJson = Json.createArrayBuilder()
// .add("newString1")
// .add("newString2").build();
private final static JsonStructure replacementJson = Json.createObjectBuilder()
.add("newkey1", "newString1")
.add("newkey2", "newString2").build();
So, using the same starting JSON as in my other answer, this code produces the following:
{
"Attr0": null,
"Attr1": true,
"Attr2": false,
"Attr3": 3,
"Attr4": [
"string1"
],
"Attr5": [
{
"Attr6": {
"newkey1": "newString1",
"newkey2": "newString2"
},
"Attr10": 6,
"Attr14": {
"Attr10": "string6",
"Attr11": "string7",
"Attr12": "string8"
},
"Attr13": [
"string9",
123.45,
false
],
"Attr15": "string11"
}
]
}
I'm sorting JSON objects in a JSON array by passing the JSON key.
My program is working only when the json value is String
Example:- {"id":"0001"}
so whenever there is integer value program throws an error.
Example:- {"id":0001}
program:-
import java.util.*;
import org.json.*;
public class sample2 {
public static void main(String[] args) {
String jsonArrStr = "[ "
+ ""
+ "{ \"ID\": \"135\", \"Name\": \"Fargo Chan\" },"
+ "{ \"ID\": \"432\", \"Name\": \"Aaron Luke\" },"
+ "{ \"ID\": \"252\", \"Name\": \"Dilip Singh\" }]";
JSONArray jsonArr = new JSONArray(jsonArrStr);
JSONArray sortedJsonArray = new JSONArray();
List<JSONObject> jsonValues = new ArrayList<JSONObject>();
for (int i = 0; i < jsonArr.length(); i++) {
jsonValues.add(jsonArr.getJSONObject(i));
}
Collections.sort(jsonValues, new Comparator<JSONObject>() {
// You can change "Name" with "ID" if you want to sort by ID
private static final String KEY_NAME = "Name";
#Override
public int compare(JSONObject a, JSONObject b) {
System.out.println("a " + a.toString());
System.out.println("b " + b.toString());
String valA = new String();
String valB = new String();
try {
valA = (String) a.get(KEY_NAME);
System.out.println("valA " + valA);
valB = (String) b.get(KEY_NAME);
System.out.println("valB " + valB);
} catch (JSONException e) {
// do something
}
return valA.compareTo(valB);
// if you want to change the sort order, simply use the following:
// return -valA.compareTo(valB);
}
});
for (int i = 0; i < jsonArr.length(); i++) {
sortedJsonArray.put(jsonValues.get(i));
}
System.out.println(sortedJsonArray.toString());
}
}
How to make above program work dynamically for all the DATA-TYPES(String,Integer,Float,Double).
There are not many ways to to what you want to achieve. Refelection API is there but it comes with many drawbacks and not very reliable and straightforward. However, the most reliable and easy way is to use instanceof operator.
With this method you have full control over every conversion and comparison, also you can compare custom classes with compareTo() method implemented. Unless you have too many possibilities to cover, this maybe the best approach.
I assume that you know that JSONObject.get() method converts object into Object types so add conditions like
Object obj1 = a.get(KEY_NAME);
Object obj2 = b.get(KEY_NAME);
if(obj1 instanceof Integer && obj2 instanceof Integer){
return ((Integer) obj1).compareTo(((Integer) obj2));
} else if(obj1 instanceof Double && obj2 instanceof Double){
return ((Double) obj1).compareTo(((Double) obj2));
} else if(obj1 instanceof Double || obj2 instanceof Double){
Double v1 = ((Number) obj1).doubleValue();
Double v2 = ((Number) obj2).doubleValue();
return v1.compareTo(v2);
}
As Number class is super class of Integer, Float and Double, so you
can convert it from there.
It may induce lot of code but will add reliability but no surprises when unexpexcted JSON is encountered. Also additional conditions would be able handle to handle mismatching data types and and could also detect error in JSON object.
I have a list of java objects, and I want to use stream to filter them at runtime. However, the variable I want to filter on is only known at runtime.
For eg, the user says I want a list of all cats whose fur length is longer than 3cm, I should be able to do
cats.stream().filter(cat -> cat.getFurLength() > 3).collect(Collectors.toList());
The getFurLength() getter however should be dynamically invoked - if the user instead wants to filter by eye colour then I should be able to call
cats.stream().filter(cat -> cat.getEyeColour() == Colour.BLUE).collect(Collectors.toList());
How do I achieve this without writing all possible filters beforehand?
Ideally the user should send something like:
{
eyeColour:{
operator: "equal_to",
value: "BLUE"
},
furLength: {
operator: "greater_than",
value: 3
}
}
and the code should be able to generate the filters dynamically based on these criteria.
Assuming your Cat class follows JavaBean convention you could use java.beans.PropertyDescriptor to access getter Method based on property name.
This allows us to learn what type of value we are dealing with. If it is numeric we can handle greater_than and other operators, but if it is non-numeric we should handle only equals_to operator.
"Simplified" and very limited solution could look like:
NOTE:
- Solution doesn't support primitive numeric types like int. Use Integer, Double etc. instead.
- I am converting all numbers to BigDecimal and use compareTo to simplify numerical type comparison, if you get any bugs for big numbers or very precise ones feel free to replace it with proper type comparison).
- for equality check it compares string representation of objects (result of toString()), so for Color you can't use BLUE but your JSON would need to hold java.awt.Color[r=0,g=0,b=255])
class PredicatesUtil {
static <T> Predicate<T> filters(Class<T> clazz, String filtersJson) {
JSONObject jsonObject = new JSONObject(filtersJson);
List<Predicate<T>> predicateList = new ArrayList<>();
for (String property : jsonObject.keySet()) {
JSONObject filterSettings = jsonObject.getJSONObject(property);
try {
String operator = filterSettings.getString("operator");
String value = filterSettings.getString("value");
predicateList.add(propertyPredicate(clazz, property, operator, value));
} catch (IntrospectionException e) {
throw new RuntimeException(e);
}
}
return combinePredicatesUsingAND(predicateList);
}
static <T> Predicate<T> combinePredicatesUsingAND(List<Predicate<T>> predicateList) {
return t -> {
for (Predicate<T> pr : predicateList) {
if (!pr.test(t))
return false;
}
return true;
};
}
static <T> Predicate<T> propertyPredicate(Class<T> clazz, String property,
String operator, String value)
throws IntrospectionException {
final Method m = new PropertyDescriptor(property, clazz).getReadMethod();
final Class<?> returnType = m.getReturnType();
return obj -> {
try {
Object getterValue = m.invoke(obj);
if (Number.class.isAssignableFrom(returnType)) {
BigDecimal getValue = new BigDecimal(getterValue.toString());
BigDecimal numValue = new BigDecimal(value);
int compared = getValue.compareTo(numValue);
if (operator.equalsIgnoreCase("equal_to")) {
return compared == 0;
} else if (operator.equalsIgnoreCase("lesser_than")) {
return compared < 0;
} else if (operator.equalsIgnoreCase("greater_than")) {
return compared > 0;
} else {
throw new RuntimeException("not recognized operator for numeric type: " + operator);
}
} else {
//System.out.println("testing non-numeric, only euals_to");
if (operator.equalsIgnoreCase("equal_to")) {
return value.equalsIgnoreCase(getterValue.toString());
}
throw new RuntimeException("not recognized operator: " + operator);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
};
}
}
which can be used like:
class Cat {
private Color eyeColour;
private Integer furLength;
Cat(Color eyeColor, Integer furLength) {
this.eyeColour = eyeColor;
this.furLength = furLength;
}
public Color getEyeColour() {
return eyeColour;
}
public Integer getFurLength() {
return furLength;
}
public void setEyeColour(Color eyeColour) {
this.eyeColour = eyeColour;
}
public void setFurLength(Integer furLength) {
this.furLength = furLength;
}
#Override
public String toString() {
return "Cat{" +
"eyeColor=" + eyeColour +
", furLength=" + furLength +
'}';
}
}
class CatsDemo {
public static void main(String[] args) {
String json =
"{\n" +
" eyeColour:{\n" +
" operator: \"equal_to\",\n" +
" value: \"java.awt.Color[r=0,g=0,b=255]\"\n" +
" },\n" +
" furLength: {\n" +
" operator: \"greater_than\",\n" +
" value: \"3\"\n" +
" }\n" +
"}";
List<Cat> cats = List.of(
new Cat(Color.blue, 1),
new Cat(Color.blue, 2),
new Cat(Color.blue, 3),
new Cat(Color.blue, 4),
new Cat(Color.blue, 5),
new Cat(Color.yellow, 1),
new Cat(Color.yellow, 2),
new Cat(Color.yellow, 3),
new Cat(Color.yellow, 4),
new Cat(Color.yellow, 5)
);
cats.stream()
.filter(PredicatesUtil.filters(Cat.class, json))
.forEach(System.out::println);
}
}
Output:
Cat{eyeColor=java.awt.Color[r=0,g=0,b=255], furLength=4}
Cat{eyeColor=java.awt.Color[r=0,g=0,b=255], furLength=5}
Make it reusable with a function.
List<Cat> filterCats(cats, Predicate<Cat> filter) {
return cats.stream().filter(filter).collect(Collectors.toList());
}
And then use it with:
filterCats(cats, cat -> cat.getEyeColour() == Colour.BLUE)
Or,
filterCats(cats, cat -> cat.getFurLength() > 3)
For what it's worth: Apache Commons BeanUtils library is specialized in accessing bean properties in a dynamic way.
See BeanPropertyValueEqualsPredicate for an understanding. This is only a solution for equality matches.
I have an JSON data
{
"HiServiceInquiryResponse": {
"CoverageInfoResponse": {
"Participant": {
"ns1:PersonalInfo": {
"ns1:LastName": "AA",
"ns1:Address": [
{
"ns1:Province": "",
"ns1:State": "CA",
"ns1:City": "LOS ANGELES",
"ns1:Country": "US",
"ns1:Address2": "",
"ns1:Address1": "test",
"ns1:PostalCode": 12345
},
{
"ns1:Province": "",
"ns1:State": "CA",
"ns1:City": "LOS ANGELES",
"ns1:Country": "US",
"ns1:Address2": "",
"ns1:Address1": "test",
"ns1:PostalCode": 12345
}
],
"ns1:FirstName": "BB"
},
"ns1:Coverage": "",
"ns1:HiClientId": 57,
"ns1:Employment": {
"ns1:EmployeeId": 1234,
"ns1:TaxId": 111
}
}
}
}
}
I want to read all the key-value pairs and store them. So far I am able to do it
public static void printJsonObject(JSONObject jsonObj) {
for (Object key : jsonObj.keySet()) {
String keyStr = (String) key;
Object keyvalue = jsonObj.get(keyStr);
if (!(keyvalue instanceof JSONObject)) {
System.out.println(keyStr + ", " + keyvalue);
}
if (keyvalue instanceof JSONObject) {
printJsonObject((JSONObject) keyvalue);
}
}
}
The problem is when we have 2 address in the personalInfo it does not read them separately.
My Output when there is only 1 address: ->
ns1:LastName, AA
ns1:Province,
ns1:State, CA
ns1:City, LOS ANGELES
ns1:Country, US
ns1:Address2,
ns1:Address1, test
ns1:PostalCode, 12345
ns1:FirstName, BB
ns1:Coverage,
ns1:HiClientId, 57
ns1:EmployeeId, 1234
ns1:TaxId, 111
My Output when there are 2 address: ->
ns1:LastName, AA
ns1:Address, [{"ns1:Province":"","ns1:State":"CA","ns1:City":"LOS ANGELES","ns1:Country":"US","ns1:Address2":"","ns1:Address1":"test","ns1:PostalCode":12345},{"ns1:Province":"","ns1:State":"CA","ns1:City":"LOS ANGELES","ns1:Country":"US","ns1:Address2":"","ns1:Address1":"test","ns1:PostalCode":12345}]
ns1:FirstName, BB
ns1:Coverage,
ns1:HiClientId, 57
ns1:EmployeeId, 1234
ns1:TaxId, 111
I want the data should be displayed for both addresses.
To parse array inside JSONObject you have to check value instance of JSONArray and recursively call printJsonObject for each array item:
public static void printJsonObject(JSONObject jsonObj) {
for (Object key : jsonObj.keySet()) {
Object value = jsonObj.get(key);
if (value instanceof JSONObject)
printJsonObject((JSONObject)value);
else if (value instanceof JSONArray)
((JSONArray)value).forEach(obj -> printJsonObject((JSONObject)obj));
else
System.out.println(key + ", " + value);
}
}
this should solve your problem
public static void printJsonObject(JSONObject jsonObj) {
for (Object key : jsonObj.keySet()) {
String keyStr = (String) key;
Object keyvalue = jsonObj.get(keyStr);
if (keyvalue instanceof JSONObject) {
printJsonObject((JSONObject) keyvalue);
} else if (keyvalue instanceof JSONArray) {
JSONArray array = (JSONArray) keyvalue;
for (int i = 0; i < array.length(); i++) {
printJsonObject((JSONObject) array.get(i));
}
} else {
System.out.println(keyStr + ", " + keyvalue);
}
}
}
change your code as
if (!(keyvalue instanceof JSONObject)) {
if(keyStr.equals("ns1:Address")){
//now it is your array so loop through it and call printJsonObject((JSONObject) keyvalue); on each object
}
else{
System.out.println(keyStr + ", " + keyvalue);
}
}
This is happening because when there are two addresses, the JSONObject corresponding to the address is an array. If you want it to be printed separately, check whether it is an instanceOf JSONArray. Then parse through the different addresses in the array. If it is not an array, just print it.
I could not find out how to traverse a JSON-tree (nested structure) and decide on the keys of the elements what to expect next and traverse from node to node. Like this (pseudocode):
int traverse(node) {
if (node.key == ADD) {
int res = 0;
for (child:node.children)
res = res + traverse(child);
}
if (node.key == "ADD") {
int res = 0;
for (child:node.children)
res = res + traverse(children);
}
if (node.key == "MULT") {
int res = 0;
for (child:node.children)
res = res * traverse(children);
}
if (node.key == "INT")
return node.value;
}
The json-string to parse could be like this:
{"ADD":[{"INT":"1"},{"INT":"3"},{"INT":"4"}]}
or this:
{"ADD":[{"INT":"1"},{"INT":"3"},{"ADD":[{"INT":"5"},{"INT":"6"}]},
{"INT":"4"}]}
How could I use JSON-Object or JSON-Arrays and
inside the objects access the key and value variables to traverse through this tree recursively?
EDITED:
After all the comments I try to put this as first running example
(still looks a little uneasy to me, but it works):
public static int evaluate(javax.json.JsonObject node) {
Set<?> keySet = node.keySet();
Iterator<?> i = keySet.iterator();
if (i.hasNext()) {
String key = i.next().toString();
System.out.println("key: " + key);
if (key.equals("ADD")) {
JsonArray ja = node.getJsonArray("ADD");
int res = 0;
for (JsonValue jvx: ja) {
if (jvx.getValueType().toString().equals("OBJECT")) {
res = res + evaluate((JsonObject)jvx);
} else{
System.err.println("jvx should not be a " + jvx.getValueType().toString() + " here");
}
}
return res;
}
if (key.equals("MULT")) {
JsonArray ja = node.getJsonArray("MULT");
int res = 1;
for (JsonValue jvx: ja) {
if (jvx.getValueType().toString().equals("OBJECT")) {
res = res * evaluate((JsonObject)jvx);
} else{
System.err.println("jvx should not be a " + jvx.getValueType().toString() + " here");
}
}
return res;
}
if (key.equals("INT")) {
String intStr = node.getString("INT");
System.out.println ("found int = " + intStr);
return Integer.parseInt(intStr);
}
}
return 0;
}
public static void readJSON() {
String jsonText = "{\"ADD\":[{\"INT\":\"1\"},{\"INT\":\"3\"},{\"ADD\":[{\"INT\":\"5\"},{\"INT\":\"6\"}]},{\"INT\":\"4\"}]}";
JsonReader reader = Json.createReader(new StringReader(jsonText));
JsonObject obj = reader.readObject();
reader.close();
int res = evaluate(obj);
System.out.println("res: " + res);
}
Your evaluating pseudocode is OK (just pay attention to the initial value when multiplying!). To adapt it to the javax.json hierarchy, you should code your evaluating method like this:
int evaluate(javax.json.JsonObject node):
Get each on of the admitted keys (ADD, MULT, INT, etc) through node.getJsonObject(key): In case it returns null, check the next admitted key, and stop at the first you find.
On each operation, a proper logic must be coded:
In case the key is a constant value (INT), return its value immediately.
In case the key is an operation, check the value's type (through node.getValueType()): If it is a single value, return it as is. If it is an array, iterate through its elements and call evaluate for each one of them, and perform the proper operation with the returned value (adding, multiplying, etc). Last, return the computation's result.
After your first edit
Your first real approach looks OK; I'd just suggest you some improvements to make the code more readable:
Use an enhanced for.
Replace the if-else chanin by a switch.
Replace each case by a call to a private method.
int result;
Set<String> keySet = node.keySet();
for (String key : keySet)
{
switch (key)
{
case "ADD":
result=evaluateAdd(node.getJsonArray("ADD"));
break;
case "MULT":
result=evaluateMult(node.getJsonArray("ADD"));
break;
case "INT":
result=node.getInt("INT");
break;
...
}
}
JSON [{
"name": "com",
"children": [
{
"name": "project",
"children": [
{
"name": "server"
},
{
"name": "client",
"children": [
{
"name": "util"
}
]
}
]
}
]
}]
Try this:
public static JSONArray getNames(JSONArray inputArray, JSONArray outputArray) {
for (int i = 0; i < inputArray.length(); i++) {
JSONObject inputObject = inputArray.getJSONObject(i);
JSONObject outputObject = new JSONObject();
outputObject.put("name", inputObject.getString("name"));
outputArray.put(outputObject);
if (inputObject.has("children")) {
JSONArray children = inputObject.getJSONArray("children");
getNames(children, outputArray);
}
}
return outputArray;
}