I have a json object nested as the example below and I want to check an item inside that object whether what it should be ===> Could you tell me how to do it?
E.g: I want to check whether the item contract_date is in valid format as yyyymmdd hh:mm:ss
{
"m_list" : [ {
"contract" : {
"category" : 1,
"cor_num" : 101,
"contract_name" : "ABC",
"contract_date" : "20230220 15:30:11"
},
"bu_unit" : {
"bu_name" : "1-1E"
}
} ]
}
I tried doing the following way but Katalon showed this error: No signature of method: static CommonFc.getAllKeys() is applicable for argument types: (java.util.LinkedHashMap, java.util.ArrayList
Possible solutions: getAllKeys(java.util.Map, java.util.List)
public List<String> getKeysInJsonUsingMaps(String json, ObjectMapper mapper) {
List<String> keys = new ArrayList<>();
Map<String, Object> jsonElements = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
});
getAllKeys(jsonElements, keys);
return keys;
}
public void getAllKeys(Map<String, Object> jsonElements, List<String> keys) {
jsonElements.entrySet()
.forEach({def entry ->
keys.add(entry.getKey());
if (entry.getValue() instanceof Map) {
Map<String, Object> map = (Map<String, Object>) entry.getValue();
getAllKeys(map, keys);
} else if (entry.getValue() instanceof List) {
List<?> list = (List<?>) entry.getValue();
list.forEach({ def listEntry ->
if (listEntry instanceof Map) {
Map<String, Object> map = (Map<String, Object>) listEntry;
getAllKeys(map, keys);
}
});
}
});
}
Related
I have created a UpperCaseAdapter to upperCase all the keys of a json ,
But not working as expected
Here is my UpperCaseAdapter code , i want to also upper case the arrays part but it is not the case for me , how could correct that ?
public static class UpperCaseAdapter implements JsonSerializer<Map<String, Object>>, JsonDeserializer<Map<String, Object>> {
public static final Type TYPE = new TypeToken<Map<String, Object>>() {}.getType();
#Override
public JsonElement serialize(Map<String, Object> src, Type typeOfSrc, JsonSerializationContext context) {
// TODO implement serialization if needed
return null;
}
#Override
public Map<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Map<String, Object> map = new HashMap<>();
for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
Object value = null;
if (entry.getValue().isJsonPrimitive()) {
value = entry.getValue().getAsString();
} else if (entry.getValue().isJsonObject()) {
value = context.deserialize(entry.getValue(), TYPE); // deserialize the object using the same type
} else if (entry.getValue().isJsonArray()) {
// TODO implement deserailization of array
} else if (entry.getValue().isJsonNull()) {
// skip nulls
continue;
}
map.put(entry.getKey().toUpperCase(), value); //toUpperCase() is what we want
}
return map;
}
}
and this code is to call my UpperCaseAdapter
String payload = input_row.body;
Gson gson = new GsonBuilder()
.registerTypeAdapter(json_routine.UpperCaseAdapter.TYPE, new
json_routine.UpperCaseAdapter())
.create();
Map<String, Object> mapDeserialized = gson.fromJson(payload,
json_routine.UpperCaseAdapter.TYPE);
System.out.println("**** body_WS" + gson.toJson(mapDeserialized));
My body looks like this :
{
"transactionId": 181,
"ExtWareHouseId ": "toto",
"OwnerCode": "toto",
"ClientCode": "toto",
"activityId": toto,
"taskTypeCode": "01",
"activated": "2023-02-06T17:33:48.1905172+01:00",
"StartDate ": "2023-01-24T15:19:12.8591383+01:00",
"userCode": "toto",
"equipmentId": "01",
"PortId": "1",
"printerName": "",
"shipments": [
{
"shipmentId": 2,
"picklistId": "Example",
"ExtOrderId": "23-127",
"boxType": "120"
}
]
}
I get the following result :
{
"EXTWAREHOUSEID ":"toto",
"PRINTERNAME":"",
"TRANSACTIONID":"181",
"OWNERCODE":"toto",
"ACTIVITYID":"1072",
"EQUIPMENTID":"01",
"USERCODE":"toto",
"STARTDATE ":"2023-01-24T15:19:12.8591383+01:00",
"ACTIVATED":"2023-02-06T17:33:48.1905172+01:00",
"TASKTYPECODE":"01",
"CLIENTCODE":"toto",
"PORTID":"1"
}
The shipments part is missing , how could i correct my adapter to get a correct result
Any help on this , i'm stack on how to Upper case Array keys
I want to convert all keys of JSON string/object to UPPERCASE in Java. The JSON can be nested.
I tried setting FieldNamingPolicy.UPPER_CAMEL_CASE in GsonBuilder but I guess that just works for String to JAVA Object and not for String to String.
String payload = "{\"key\" : {\"key1\" : \"value1\",\"key2\" : \"value2\"}}";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(myCustomTypeAdapterFactory);
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
Gson gson = gsonBuilder.create();
Map mapDeserialized = gson.fromJson(payload, Map.class);
System.out.println("Map " + mapDeserialized);
There are other solutions through JACKSON with custom TypeAdapterFactory but those only work for one level and not for nested.
{"key" : {
"key1" : "value1",
"key2" : "value2"
}}
to
{"KEY" : {
"KEY1" : "value1",
"KEY2" : "value2"
}}
As you said FieldNamingPolicy is applied only for bean fields not for map keys. However UPPER_CAMEL_CASE is not what you want, it is camel case with first letter capitalized (SometingLikeThis). You have to implement your own deserializer that would do that for your:
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public class UpperCaseAdapter implements JsonSerializer<Map<String, Object>>, JsonDeserializer<Map<String, Object>> {
public static final Type TYPE = new TypeToken<Map<String, Object>>() {}.getType();
#Override
public JsonElement serialize(Map<String, Object> src, Type typeOfSrc, JsonSerializationContext context) {
// TODO implement serialization if needed
return null;
}
#Override
public Map<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Map<String, Object> map = new HashMap<>();
for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
Object value = null;
if (entry.getValue().isJsonPrimitive()) {
value = entry.getValue().getAsString();
} else if (entry.getValue().isJsonObject()) {
value = context.deserialize(entry.getValue(), TYPE); // deserialize the object using the same type
} else if (entry.getValue().isJsonArray()) {
// TODO implement deserailization of array
} else if (entry.getValue().isJsonNull()) {
// skip nulls
continue;
}
map.put(entry.getKey().toUpperCase(), value); //toUpperCase() is what we want
}
return map;
}
}
you can use the adapter then:
String payload = "{\"key\" : {\"key1\" : \"value1\",\"key2\" : \"value2\"}, \"key3\": \"value\"}";
Gson gson = new GsonBuilder()
.registerTypeAdapter(UpperCaseAdapter.TYPE, new UpperCaseAdapter())
.create();
Map<String, Object> mapDeserialized = gson.fromJson(payload, UpperCaseAdapter.TYPE);
System.out.println("Map " + mapDeserialized);
and the output is:
Map {KEY3=value, KEY={KEY2=value2, KEY1=value1}}
I'm trying to migrate an old project to Retrofit library and this project has quite tricky API. So I have a query template like this:
#GET(value = "products/search")
Single<ProductSearchResponse> productSearch();
And I have to add some parameters here of following template:
filter[attributeId]=attributeValueId
For example:
products/search?filter[1]=10&filter[1]=11&filter[2]=20&filter[2]=21
That's how API works and I can't change it. I know that we can pass a list as a parameter, like this:
#Query("filter") List<Integer> attributeValueIds
But how can I also set parameter's name dynamically?
You can use an arrayList! Something like the code below.
#GET(value = "products/search")
Single<ProductSearchResponse> productSearch(
#Query("status") List<Integer> status
);
ArrayList<Integer> queryStatus = new ArrayList<>();
queryStatus.add(0);
queryStatus.add(1);
queryStatus.add(2);
productService.productSearch(queryStatus);
Your url will be like that -> {url}?status=0&status=1&status=2
Thanks to the link, posted by #ILLIA DEREVIANKO (https://github.com/square/retrofit/issues/1324), I've managed to solve the problem with this class:
public class ProxyRetrofitQueryMap extends HashMap<String, Object> {
public ProxyRetrofitQueryMap(Map<String, Object> m) {
super(m);
}
#Override
public Set<Entry<String, Object>> entrySet() {
Set<Entry<String, Object>> originSet = super.entrySet();
Set<Entry<String, Object>> newSet = new HashSet<>();
for (Entry<String, Object> entry : originSet) {
String entryKey = entry.getKey();
if (entryKey == null) {
throw new IllegalArgumentException("Query map contained null key.");
}
Object entryValue = entry.getValue();
if (entryValue == null) {
throw new IllegalArgumentException(
"Query map contained null value for key '" + entryKey + "'.");
}
else if(entryValue instanceof List) {
for(Object arrayValue:(List)entryValue) {
if (arrayValue != null) { // Skip null values
Entry<String, Object> newEntry = new AbstractMap.SimpleEntry<>(entryKey, arrayValue);
newSet.add(newEntry);
}
}
}
else {
Entry<String, Object> newEntry = new AbstractMap.SimpleEntry<>(entryKey, entryValue);
newSet.add(newEntry);
}
}
return newSet;
}
}
With this we can just use a map, where key is a unique parameter name and value is a List of Strings, that are values for this parameter. Something like this:
ProxyRetrofitQueryMap map = new ProxyRetrofitQueryMap();
List<String> values1 = new ArrayList<>();
values1.add("10");
values1.add("11");
map.put("filter[1]", values1);
List<String> values2 = new ArrayList<>();
values1.add("20");
values1.add("21");
map.put("filter[2]", values2);
You can use #QueryMap annotation like this:
public interface NewsService() {
#GET("/news")
Call<List<News>> getNews(
#QueryMap Map<String, String> options
);
}
Map<String, String> data = new HashMap<>();
data.put("author", "Marcus");
data.put("page", String.valueOf(2));
...
newsService.getNews(data);
More details: https://futurestud.io/tutorials/retrofit-2-add-multiple-query-parameter-with-querymap
I have an enhanced question regarding Flatten a JSON string to Map using Gson or Jackson.
My scenario included duplicated keys, so the solution in the above question will cause some duplicated keys overwritten. So I am thinking to construct keys by combining each level's key together.
So how to achieve that?
For example:
{
"id" : "123",
"name" : "Tom",
"class" : {
"subject" : "Math",
"teacher" : "Jack"
}
}
I want to get the Map:
"id" : "123",
"name" : "Tom",
"class.subject" : "Math",
"class.teacher" : "Jack"
************************Update Solution*************************************
Based on #Manos Nikolaidis's answer, I am able to achieve the following solution by considering ArrayNode.
public void processJsonString(String jsonString) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = (ArrayNode) mapper.readTree(jsonString);
processArrayNode(arrayNode);
}
private void processObjectNode(JsonNode jsonNode) {
Map<String, String> result = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> iterator = jsonNode.fields();
iterator.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
}
private void processArrayNode(ArrayNode arrayNode) {
for (JsonNode jsonNode : arrayNode) {
processObjectNode(jsonNode);
}
}
private void mapAppender(Map<String, String> result, Map.Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, node.getValue().asText());
} else if (node.getValue().isArray()) {
processArrayNode((ArrayNode) node.getValue());
} else if (node.getValue().isNull()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, null);
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
You can get the JSON as JsonNode and go through all fields recursively and add key and value field to a Map. When a value is an object instead of string you can add the field name to List to be joined with periods when a string is finally encountered. First create (for readability) a separate method that add Json fields to a Map:
void mapAppender(Map<String, String> result, Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(joining("."));
result.put(name, node.getValue().asText());
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
and use it like this:
ObjectMapper mapper = new ObjectMapper();
Map<String, String> result = new HashMap<>();
mapper.readTree(json).fields()
.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
Where fields() returns an Iterator. Beware of StackOverflowErrors and perhaps low performance for deeply nested JSON.
I resolved this using below simple code, Only think is need to download jettison and flattener.JsonFlattener library
import java.util.Map;
import org.codehaus.jettison.json.JSONObject;
import com.github.wnameless.json.flattener.JsonFlattener;
public class test {
public static void main(String[] args) {
String jsonString = "{\"id\" : \"123\",\"name\" : \"Tom\",\"class\" : {\"subject\" : \"Math\",\"teacher\" : \"Jack\"}}";
JSONObject jsonObject = new JSONObject();
String flattenedJson = JsonFlattener.flatten(jsonString);
Map<String, Object> flattenedJsonMap = JsonFlattener.flattenAsMap(jsonString);
System.out.println(flattenedJsonMap);
}
}
Reference link : https://github.com/wnameless/json-flattener
I have an enhanced question regarding Flatten a JSON string to Map using Gson or Jackson.
My scenario included duplicated keys, so the solution in the above question will cause some duplicated keys overwritten. So I am thinking to construct keys by combining each level's key together.
So how to achieve that?
For example:
{
"id" : "123",
"name" : "Tom",
"class" : {
"subject" : "Math",
"teacher" : "Jack"
}
}
I want to get the Map:
"id" : "123",
"name" : "Tom",
"class.subject" : "Math",
"class.teacher" : "Jack"
************************Update Solution*************************************
Based on #Manos Nikolaidis's answer, I am able to achieve the following solution by considering ArrayNode.
public void processJsonString(String jsonString) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = (ArrayNode) mapper.readTree(jsonString);
processArrayNode(arrayNode);
}
private void processObjectNode(JsonNode jsonNode) {
Map<String, String> result = new HashMap<>();
Iterator<Map.Entry<String, JsonNode>> iterator = jsonNode.fields();
iterator.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
}
private void processArrayNode(ArrayNode arrayNode) {
for (JsonNode jsonNode : arrayNode) {
processObjectNode(jsonNode);
}
}
private void mapAppender(Map<String, String> result, Map.Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, node.getValue().asText());
} else if (node.getValue().isArray()) {
processArrayNode((ArrayNode) node.getValue());
} else if (node.getValue().isNull()) {
String name = names.stream().collect(Collectors.joining("."));
result.put(name, null);
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
You can get the JSON as JsonNode and go through all fields recursively and add key and value field to a Map. When a value is an object instead of string you can add the field name to List to be joined with periods when a string is finally encountered. First create (for readability) a separate method that add Json fields to a Map:
void mapAppender(Map<String, String> result, Entry<String, JsonNode> node, List<String> names) {
names.add(node.getKey());
if (node.getValue().isTextual()) {
String name = names.stream().collect(joining("."));
result.put(name, node.getValue().asText());
} else {
node.getValue().fields()
.forEachRemaining(nested -> mapAppender(result, nested, new ArrayList<>(names)));
}
}
and use it like this:
ObjectMapper mapper = new ObjectMapper();
Map<String, String> result = new HashMap<>();
mapper.readTree(json).fields()
.forEachRemaining(node -> mapAppender(result, node, new ArrayList<String>()));
Where fields() returns an Iterator. Beware of StackOverflowErrors and perhaps low performance for deeply nested JSON.
I resolved this using below simple code, Only think is need to download jettison and flattener.JsonFlattener library
import java.util.Map;
import org.codehaus.jettison.json.JSONObject;
import com.github.wnameless.json.flattener.JsonFlattener;
public class test {
public static void main(String[] args) {
String jsonString = "{\"id\" : \"123\",\"name\" : \"Tom\",\"class\" : {\"subject\" : \"Math\",\"teacher\" : \"Jack\"}}";
JSONObject jsonObject = new JSONObject();
String flattenedJson = JsonFlattener.flatten(jsonString);
Map<String, Object> flattenedJsonMap = JsonFlattener.flattenAsMap(jsonString);
System.out.println(flattenedJsonMap);
}
}
Reference link : https://github.com/wnameless/json-flattener