I have this json Input
[
{
"id": "32",
"name": "Menu full"
"description": "Here is the object {id} is {name}"
},
{
"id": "60",
"name": "Side bar menu"
"description": "Here is the object {id}",
"children": [
{
"id": "60.1",
"name": ""
"description": "Here is the object {id} is {name}",
"children": []
}
]
}
]
Here is the java class representation.
public class Menu {
String id;
String name;
String description;
List <Menu> children;
//Getters and Setters
}
This is a recursive java object since the children is the same type as the parent.
I need to search and replace all variables inside "{}" with the value of the search by key using the value obtained from the string in {}. from the given json this should be the output.
Output
[
{
"id": "32",
"name": "Menu full"
"description": "Here is the object 32 is Menu full"
},
{
"id": "60",
"name": "Side bar menu"
"description": "Here is the object 60",
"children": [
{
"id": "60.1",
"name": ""
"description": "Here is the object 60.1 is Side bar menu",
"children": []
}
]
}
]
As you can see in the children there was no "{name}" in the current object to replace for. In that case the logic should get the "name" value from the key "name" obtained from the parent. If the parent dont have that value in the key (null) or is empty then the replace for the children should be String.empty
In order to achieve this I transformed the java object to a Map<String, Object> since I need to first determinate if the value has a replacement String ("{value}") and then search the key based on that replacement String.
Assuming I have already transformed the object to a Map.
private void ReplaceAllVariablesMap (Map<String, Object> objectMap) {
objectMap.entrySet()
.forEach(entry -> {
String value = Optional.ofNullable(entry.getValue()).orElse("").toString();
List<String> valuesToReplace = getValuesToReplace(value);
if(entry.getValue() instanceof String && valuesToReplace.size() > 0){
valuesToReplace.forEach(v -> {
String valueToReplaceWith = getValueByKey(objectMap, getValueFromBracketString(v));
value.replace(v, valueToReplaceWith);
});
}
if (entry.getValue() instanceof Map) {
Map<String, Object> map = (Map<String, Object>) entry.getValue();
ReplaceAllVariablesMap(map);
} else if (entry.getValue() instanceof List) {
List<?> list = (List<?>) entry.getValue();
list.forEach(listEntry -> {
if (listEntry instanceof Map) {
Map<String, Object> map = (Map<String, Object>) listEntry;
ReplaceAllVariablesMap(map);
}
});
}
});
}
private List<String> getValuesToReplace(String value){
return Pattern.compile("\\{(.*?)\\}")
.matcher(value)
.results()
.map(MatchResult::group)
.collect(Collectors.toList());
}
private String getValueByKey (Map<String, Object> objectMap, String value) {
String foundValue = null;
for (var entry : objectMap.entrySet()){
if(entry.getValue() instanceof String && entry.getKey().equalsIgnoreCase(value)){
foundValue = entry.getValue().toString();
break;
}
if (entry.getValue() instanceof Map) {
Map<String, Object> map = (Map<String, Object>) entry.getValue();
getValueByKey(map, value);
} else if (entry.getValue() instanceof List) {
List<?> list = (List<?>) entry.getValue();
list.forEach(listEntry -> {
if (listEntry instanceof Map) {
Map<String, Object> map = (Map<String, Object>) listEntry;
getValueByKey(map, value);
}
});
}
}
return foundValue;
}
private String getValueFromBracketString(String bracketValue) {
return bracketValue.replaceAll("\\{(.*?)\\}", "$1");
}
I am really struggling on how to get the parent object. Also I have to iterate to whole Map first to locate the value with "{}" to replace and then to search for the key.
The concrete question is, How to get the parent object? and any approach on how to improve this logic.
I think you can add an argument for parent. That is...
private void ReplaceAllVariablesMap (Map<String, Object> objectMap, Map<String, Object> parent) {
And then you have to put parent when calling it.
ReplaceAllVariablesMap(map, lastEntry);
Otherwise, you need to add a member in "Menu" for "parent" so that you can get it.
public class Menu {
Menu parent;
Related
I have a JSON as follows
[
{
"a": "John",
"id": "6",
"c": "val1"
},
{
"a": "Jack",
"id": "6",
"c": "val2"
},
{
"a": "Joe",
"id": "6",
"c": "val3"
}
]
I need to convert it into a Map<String, String> such that the values of the fields 'a' become the key and the values of the fields 'c' become the value in the Map.
In other words, my Map should look like the below:
John:val1
Jack:val2
Joe:val3
What is the shortest way to do this?
Also, I was wondering if in any way RestAssured GPath can be leveraged here
Something like this -
new JsonPath(jsonPayload).getString("findAll { json -> json.id == '6' }.a");
Are u looking for JsonSlurper ?
import groovy.json.JsonSlurper
String json = '''
[
{
"a": "John",
"id": "6",
"c": "val1"
},
{
"a": "Jack",
"id": "6",
"c": "val2"
},
{
"a": "Joe",
"id": "6",
"c": "val3"
}
]
'''
def root = new JsonSlurper().parseText(json)
def result = root.findAll{it.id == '6'}.collectEntries{[it.a, it.c]}
print(result)
You can use jackson for example:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2.2</version>
</dependency>
I would create a wrapper for the resulting Map and a custom deserializer.
#JsonDeserialize(using = MapWrapperDeserializer.class)
public class MapWrapper {
private final Map<String, String> map;
public MapWrapper(Map<String, String> map) {
this.map = map;
}
public Map<String, String> getMap() {
return this.map;
}
}
The deserializer:
public class MapWrapperDeserializer extends StdDeserializer<MapWrapper> {
public MapWrapperDeserializer() {
super(MapWrapper.class);
}
#Override
public MapWrapper deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode array = parser.getCodec().readTree(parser);
int size = array.size();
Map<String, String> map = new LinkedHashMap<>(size);
for (JsonNode element : array) {
String key = element.get("a").asText();
String value = element.get("c").asText();
map.put(key, value);
}
return new MapWrapper(map);
}
}
A simple test:
public class Temp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
InputStream dataStream = getInputStreamOrJsonString();
MapWrapper wrapper = mapper.readValue(dataStream, MapWrapper.class);
System.out.println(wrapper.getMap());
}
}
You can use jsonpath library to make it. Add it into your pom.xml:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
Then try following code
// read the a, c of json string by the JsonPath libraby
List<String> aList = JsonPath.read(json, "$.[*].a");
List<String> cList = JsonPath.read(json, "$.[*].c");
// combine two list to a map
Iterator<String> i1 = aList.iterator();
Iterator<String> i2 = cList.iterator();
Map<String, String> map = new HashMap<>();
while (i1.hasNext() && i2.hasNext()) {
map.put(i1.next(), i2.next());
}
// print it
map.forEach((k,v) -> System.out.println(k + ":" + v));
See more about jsonpath
You can convert it first to a list of YourObject and them convert it to a map following the rules you want ( key = a, value = c)
Create a class that represent the json object:
class YourObject
{
String a;
String id;
String c;
// contructors
// getters and setters
}
Desserialize your JSON into it using Gson:
String json = "<PUT YOUR JSON HERE>";
List<YourObject> list = new GsonBuilder().create().fromJson(json, new TypeToken<List<YourObject>>(){}.getType());
Then transform it to a map:
Map<String, String> map = list.stream().collect(Collectors.toMap(YourObject::getA, YourObject::getC));
If you want to filter by an specific id (forxample id=6) you can do like that:
Map<String, String> map = list.stream()
.filter(yo -> yo.getId().equals("6"))
.collect(Collectors.toMap(YourObject::getA, YourObject::getC));
How can I fetch all the nested keys of JSON object?
Below is the JSON input and it should return all the keys and subkeys with dot-separated like below output.
Input:
{
"name": "John",
"localizedName": [
{
"value": "en-US",
}
],
"entityRelationship": [
{
"entity": "productOffering",
"description": [
{
"locale": "en-US",
"value": "New Policy Description"
},
{
"locale": "en-US",
"value": "New Policy Description"
}
]
}
]
}
Output:
["name","localizedName","localizedName.value","entityRelationship","entityRelationship.entity","entityRelationship.description","entityRelationship.description.locale","entityRelationship.description.value"]
You can do:
public void findAllKeys(Object object, String key, Set<String> finalKeys) {
if (object instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) object;
jsonObject.keySet().forEach(childKey -> {
findAllKeys(jsonObject.get(childKey), key != null ? key + "." + childKey : childKey, finalKeys);
});
} else if (object instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) object;
finalKeys.add(key);
IntStream.range(0, jsonArray.length())
.mapToObj(jsonArray::get)
.forEach(jsonObject -> findAllKeys(jsonObject, key, finalKeys));
}
else{
finalKeys.add(key);
}
}
Usage:
Set<String> finalKeys = new HashSet<>();
findAllKeys(json, null, finalKeys);
System.out.println(finalKeys);
Output:
[entityRelationship.entity, localizedName, localizedName.value, entityRelationship, name, entityRelationship.description.value, entityRelationship.description, entityRelationship.description.locale]
I have JSON structure like this:
{
"person":
{
"name": "snoop",
"age": "22",
"sex": "male"
}
}
And beans like this:
public class Person {
HashMap<String,String> map;
//getters and setters
}
I want all key and value from JSON to be filled inside map from Person class.
I do not want to create bean for each key from JSON like int age, String name etc.
I have tried following example and it worked for normal JSON structure like below:
{
"type": "Extends",
"target": "application",
"ret": "true"
}
and program is:
String jsonString = "{\"type\": \"Extends\", \"target\": \"application\", \"ret\":\"true\" }";
ObjectMapper mapper = new ObjectMapper();
try {
Map<String, Object> carMap = mapper.readValue(jsonString, new TypeReference<Map<String, Object>>() {
});
for (Entry<String, Object> entry : carMap.entrySet()) {
System.out.println("key=" + entry.getKey() + " and value=" + entry.getValue());
}
} catch (Exception e) {
e.printStackTrace();
}
Above program works fine and gets values in Hashmap from JSON. But in case of previously mentioned Hashmap:
{
"person":
{
"name": "snoop",
"age": "22",
"sex": "male"
}
}
it doesn't work and gives NullPointerException as Hashmap is null.
Is there any way we can use Jackson API to populate Hashmap within Person class.
use JsonAnyGetter and JsonAnySetter annotation here.
E.g:
Perosn class:
#JsonRootName("person")
class Person {
HashMap<String, String> map = new HashMap<>();
#JsonAnyGetter
public Map<String, String> getMap() {
return map;
}
#JsonAnySetter
public void setMap(String name, String value) {
map.put(name, value);
}
}
Deserialization logic:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
Person person = mapper.readValue(jsonString, Person.class);
For example:
{
"id" : "123",
"name" : "Tom",
"class" : {
"subject" : "Math",
"teacher" : "Jack"
}
}
I want to get the Map<String, String>:
"id" : "123",
"name" : "Tom",
"subject" : "Math",
"teacher" : "Jack"
I'm not sure if something exists out of the box (speaking about Gson). However you could write a custom recursive deserializer:
Type t = new TypeToken<Map<String, String>>(){}.getType();
Gson gson = new GsonBuilder().registerTypeAdapter(t, new FlattenDeserializer()).create();
Map<String, String> map = gson.fromJson(new FileReader(new File("file")), t);
...
class FlattenDeserializer implements JsonDeserializer<Map<String, String>> {
#Override
public Map<String, String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Map<String, String> map = new LinkedHashMap<>();
if (json.isJsonArray()) {
for (JsonElement e : json.getAsJsonArray()) {
map.putAll(deserialize(e, typeOfT, context));
}
} else if (json.isJsonObject()) {
for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
if (entry.getValue().isJsonPrimitive()) {
map.put(entry.getKey(), entry.getValue().getAsString());
} else {
map.putAll(deserialize(entry.getValue(), typeOfT, context));
}
}
}
return map;
}
}
Given your example it outputs:
{id="123", name="Tom", subject="Math", teacher="Jack"}
although it doesn't handle the case when a key is mapped to an array of primitives (because it's not possible to map a key with multiple values in a Map<String, String> (unless you can take the String representation of the array or that you can return a Map<String, Object>) but it works for an array of objects (given that each object has a unique key).
So if you have:
{
"id": "123",
"name": "Tom",
"class": {
"keys": [
{
"key1": "value1"
},
{
"key2": "value2"
}
],
"teacher": "Jack"
}
}
it'll output:
{id="123", name="Tom", key1="value1", key2="value2", teacher="Jack"}
You can customize it as you need if more cases are needed to be handled.
Hope it helps! :)
Use ObjectMapper to convert Json to an Java Class.Later have an Map in it as a variable.
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(new File("c:\\user.json"), User.class);
For more reference.
Using Jackson:
private static <T> T fromJsonToGenericPojo(ObjectMapper objectMapper,
String json, Class<?> classType, Class<?>... genericTypes)
{
JavaType javaType = TypeFactory.defaultInstance()
.constructParametricType(classType, genericTypes);
try {
return objectMapper.readValue(json, javaType);
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
throw new IllegalStateException(e);
}
}
you can get a map of maps:
Map<String, Object> map = fromJsonToGenericPojo(objectMapper, json,
Map.class, String.class, Object.class);
Once you have the map you can flatten it using the JDK as you wish. There isn't a generic way to flatten a map; what if you have the JSON:
{
"id" : "123",
"name" : "Tom",
"class" : [
{
"subject" : "Math",
"teacher" : "Jack"
},
{
"subject" : "English",
"teacher" : "John"
}]
}
Regarding elements in an array you will have conflicting keys. What will your resolution be? Only the last value, only the first value, will you change the key name from subject to subject1 - "Math", subject2 - "English", will you not add any of them?
I have String object with template base, something like:
<h1>{{header}}</h1>
{{#bug}}
{{/bug}}
{{#items}}
{{#first}}
<li><strong>{{name}}</strong></li>
{{/first}}
{{#link}}
<li>{{name}}</li>
{{/link}}
{{/items}}
{{#empty}}
<p>The list is empty.</p>
{{/empty}}
I want to pull another String object representing JSONObject and put its fields into template:
{
"header": "Colors",
"items": [
{"name": "red", "first": true, "url": "#Red"},
{"name": "green", "link": true, "url": "#Green"},
{"name": "blue", "link": true, "url": "#Blue"}
],
"empty": false
}
In the end I would get String representing HTML structure:
<h1>Colors</h1>
<li><strong>red</strong></li>
<li>green</li>
<li>blue</li>
I don't want to use any POJOs or Maps - only use standard String objects or alternatively convert second String into JSONObject to use it as a template's context.
Could someone give me any example how to achieve that?
Thanks.
Edit: I don't know anything about template/JSON structure while executing template - I have to play with unknown template/JSON and assume that they are correct.
I couldn't find way to work on pure String objects - I am converting JSONObject to Map to get it working with Mustache. This is code for conversion:
public static Map<String, Object> toMap(JSONObject object) throws JSONException
{
Map<String, Object> map = new HashMap();
Iterator keys = object.keys();
while (keys.hasNext())
{
String key = (String) keys.next();
map.put(key, fromJson(object.get(key)));
}
return map;
}
public static List toList(JSONArray array) throws JSONException
{
List list = new ArrayList();
for (int i = 0; i < array.length(); i++)
{
list.add(fromJson(array.get(i)));
}
return list;
}
private static Object fromJson(Object json) throws JSONException
{
if (json instanceof JSONObject)
{
return toMap((JSONObject) json);
} else if (json instanceof JSONArray)
{
return toList((JSONArray) json);
} else
{
return json;
}
}
Usage:
mustacheTemplate.execute(JSONUtils.toMap(new JSONObject(myString)));