I have a JSON object which look as the following:
[{"var1":"value1","var2":"value2"},{"var2":"value22","var3":[["0","1","2"],["3","4","5"],["6","7","8"]]}]
(Note: var2 appears twice in the example and the complex form of the value of var3.)
The desired output should be a map object like:
key value
var1 value1
var2 value2,value22
var3 [["0","1","2"],["3","4","5"],["6","7","8"]]
What I would like is to convert this to a map object with the first elements (var1, var2, var3) as keys and the corresponding values as the values in the map. In case with the identical keys (e.g.: var2) the two values which belong to this key shoul be concatenated, but separated, e.g., by a comma.
Can someone help me with this?
You don't need an adapter to parse a json. you just need to tell ObjectMapper exactly what type to parse into. you also need a bit of post processing since you want some special processing regarding duplicate keys
you get Jackson from GIT: https://github.com/FasterXML/jackson
here is a complete solution for you:
import java.util.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.type.TypeFactory;
public class Test
{
public static void main(String[] args)
{
String input = "[{\"var1\":\"value1\",\"var2\":\"value2\"},{\"var2\":\"value22\",\"var3\":[[\"0\",\"1\",\"2\"],[\"3\",\"4\",\"5\"],[\"6\",\"7\",\"8\"]]}]" ;
Map<String, String> result = new HashMap<>(); // final result, with duplicate keys handles and everything
try {
// ObjectMapper is Jackson json parser
ObjectMapper om = new ObjectMapper();
// we need to tell ObjectMapper what type to parse into
// in this case: list of maps where key is string and value is some cimplex Object
TypeFactory tf = om.getTypeFactory();
JavaType mapType = tf.constructMapType(HashMap.class, String.class, Object.class);
JavaType listType = tf.constructCollectionType(List.class, mapType);
#SuppressWarnings("unchecked")
// finally we parse the input into the data struct
List<Map<String, Object>> list = (List<Map<String, Object>>)om.readValue(input, listType);
// post procesing: populate result, taking care of duplicates
for (Map<String, Object> listItem : list) {
for (Map.Entry<String, Object> mapItem : listItem.entrySet()) {
String key = mapItem.getKey();
String value = mapItem.getValue().toString();
if (result.containsKey(key)) value = result.get(key) + "," + value;
result.put(key, value);
}
}
// result sohuld hold expected outut now
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
output:
{var3=[[0, 1, 2], [3, 4, 5], [6, 7, 8]], var2=value2,value22, var1=value1}
You can use Jackson to convert to and from JSON to Map. Use the following code and instantiate the JSonAdapter class, use the method marshal(String) to convert the json string to map and unmarshall(Map) for vice versa.
import java.io.IOException;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonAdapter {
private static final ObjectMapper MAPPER = new ObjectMapper();
public String unmarshal(final Map<?, ?> jsonList) throws Exception {
return MAPPER.writeValueAsString(jsonList);
}
public Map<?, ?> marshal(final String jsonString) throws Exception {
try {
return MAPPER.readValue(jsonString, new TypeReference<Map<?, ?>>() {
});
} catch (final IOException e) {
e.printStackTrace();
}
return null;
}
}
Related
Can I somehow alter ObjectMapper to be able to handle null and empty values?
Let's say that my value is read as
objectMapper.readValue(val, new TypeReference<Object>() {});
Where val is
val = new ByteArrayInputStream(new byte[] {});
I don't have control over value that is passed and I cannot check for buffer length prior to executing readValue.
I've tried configuring mapper with DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT such as:
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
But I still get the com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input error. Is it possible to somehow have Jackson ignore empty values and just return null?
Is it possible to somehow have Jackson ignore empty values and just return null?
You can successfully dial with an empty byte array, or an empty input stream, by using a more low-level streaming API.
That's the core idea of how you can ensure that there's some to parse by employing a JsonParser before feeding the data into an ObjectMapper:
byte[] jsonBytes1 = {};
ObjectMapper mapper = new ObjectMapper();
JsonParser parser = mapper.getFactory().createParser(jsonBytes1);
JsonNode node = parser.readValueAsTree();
MyPojo myPojo = null;
if (node != null) {
myPojo = mapper.treeToValue(node, MyPojo.class);
}
So we're parsing the input into a JsonNode and checking it manually, only if it's not null ObjectMapper comes into play.
If we extract this logic into a separate method, that's it might look like (Java 8 Optional might be handy in this case a return type):
public static <T> Optional<T> convertBytes(byte[] arr,
Class<T> pojoClass,
ObjectMapper mapper) throws IOException {
JsonParser parser = mapper.getFactory().createParser(arr);
JsonNode node = parser.readValueAsTree();
return node != null ? Optional.of(mapper.treeToValue(node, pojoClass)) : Optional.empty();
}
Usage example
Consider a simple POJO:
public class MyPojo {
private String name;
// getter, setters and toString
}
main()
public static void main(String[] args) throws IOException {
String source = """
{
"name" : "Alice"
}
""";
byte[] jsonBytes1 = {};
byte[] jsonBytes2 = source.getBytes(StandardCharsets.UTF_8);
ObjectMapper mapper = new ObjectMapper();
System.out.println(convertBytes(jsonBytes1, MyPojo.class, mapper));
System.out.println(convertBytes(jsonBytes2, MyPojo.class, mapper));
}
Output:
Optional.empty
Optional[Test.MyPojo(name=Alice)]
[{"product_name":"13X25","ask_size":0,"product_id":"5","category_id":"1","quantity":"10","image":null,"description":"","product_pdf":null,"head":"MARSHAL","price":"22.00","user_quantity":"2"},{"product_name":"14X25","ask_size":0,"product_id":"6","category_id":"1","quantity":"12","image":null,"description":"","product_pdf":null,"head":"MARSHAL","price":"23.00","user_quantity":"1"},{"product_name":"14X25 C","ask_size":0,"product_id":"2","category_id":"1","quantity":"23","image":null,"description":"","product_pdf":null,"head":"KANGARO","price":"22.00","user_quantity":"0"},{"product_name":"17X25 C","ask_size":0,"product_id":"1","category_id":"1","quantity":"18","image":null,"description":"","product_pdf":null,"head":"HOKO","price":"12.00","user_quantity":"0"}]
This is my JSON array
Where I want to remove a particular key if it is already exist
There is a key named "head": "MARSHAL", This Key comes two times but I want to fetch it only one time.
If it's coming then it must be removed automatically from other JSONObject, Not only for one key
it can be to other keys also if they get repeated then I want them only one time and after that add that JSON data to the list
How can I do it for android?
I want to show this data into cardView adapter
First of all, convert your JSON to List<Map<String, Object>>.
You can use ObhectMapper to do so.
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class Test {
private static ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) throws IOException {
List<Map<String, String>> listOfMap = createListMap();
Map<String, List<String>> tempMap = new HashMap<>();
listOfMap.forEach(
map -> {
Set<String> keySet = new HashSet<>(map.keySet());
keySet.forEach(
key -> {
List<String> tempValue = tempMap.get(key);
if (Objects.nonNull(tempValue) && tempValue.contains(map.get(key))) {
map.remove(key);
} else {
tempMap.put(key, Arrays.asList(map.get(key)));
}
});
});
System.out.println(createListMap(listOfMap));
}
private static List<Map<String, String>> createListMap() throws IOException {
File jsonFile = new File("src/main/resources/input.json");
return mapper.readValue(jsonFile, new TypeReference<List<Map<String, String>>>() {});
}
private static String createListMap(List<Map<String, String>> listOfMap) throws IOException {
return mapper.writeValueAsString(listOfMap);
}
}
Below is the output for your input JSON.
[{"product_name":"13X25","ask_size":"0","product_id":"5","category_id":"1","quantity":"10","image":null,"description":"","product_pdf":null,"head":"MARSHAL","price":"22.00","user_quantity":"2"},{"product_name":"14X25","product_id":"6","quantity":"12","price":"23.00","user_quantity":"1"},{"product_name":"14X25
C","product_id":"2","quantity":"23","head":"KANGARO","price":"22.00","user_quantity":"0"},{"product_name":"17X25
C","product_id":"1","quantity":"18","head":"HOKO","price":"12.00"}]
May this serves your purpose.
I am working on a project where I need to accept a Map called properties that is of type Map<String, Object>. There are going to be potentially many different keys in this Map, but I only care about one: xpath. An xpath can have one of three different types of values:
A string, such as {"xpath": "path/to/xml/tag"}
A List of xpaths, such as: {"xpath": ["path/to/xml/tag1", "tag2", "path/tag3"}
A Map<String, Map<String, Boolean>>, such as:
{
"xpath":
{
"path/to/xml":
{
"setting1?": true,
"setting2?": true
},
"path/tag2":
{
"setting1?": false,
"setting2": true
},
"path/to/tag3": null
}
}
Now I have three variables: String xpath, Set<String> xpaths, Map<String, Map<String, boolean> xpathMap. I have a function that is supposed to try and map the values of the "xpath" key in the properties map, and it looks like this:
private void decideXPathType(Map<String, Object> properties)
{
Object propertiesXPath = properties.get("xpath");
if (propertiesXPath instanceof String)
{
this.xpath = (String) propertiesXPath;
} else if (propertiesXPath instanceof List)
{
this.xpaths = new HashSet<String>((List) propertiesXPath);
} else if (propertiesXPath instanceof Map)
{
for (Object key : ((Map) propertiesXPath).keySet())
{
Map<String, Boolean> value = (Map<String, Boolean>) ((Map) propertiesXPath).get(key);
this.xpathMap.put((String) key, value);
}
} else
{
throw new IllegalArgumentException("the xpath value is neither String, List, or Map<String, Boolean>");
}
}
But this function looks so bad - there is so much casting, etc - and although it works, it just looks too messy, and I imagine something can go wrong... any ideas on how I can make this cleaner?
Edit: Some more details
The properties map is originally a json JsonNode requestBody that I receive from a service. Using ObjectMapper, I create a properties map as such:
Map<String, Object> properties = new ObjectMapper().convertValue(new ObjectMapper().readTree(requestBody), new TypeReference<Map<String, Object>>(){});
If I receive a json string that is the value of the xpathMap example that I gave, I get something that looks like this:
Hope this information helps?
In your JSON, use different keys for these different types of values: String, List and Map. Deserializing a map:
#Test
public void test0() throws IOException {
ObjectMapper om = new ObjectMapper();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("xpath-object.json");
JsonNode jsonNode = om.readTree(inputStream);
Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.get("xpath").toString(), Map.class);
// prints {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
System.out.println(value);
}
If you need to work with 3rd party JSON, you can use following approach:
#Test
public void test() throws IOException {
testThemAll("xpath-scalar.json");
testThemAll("xpath-array.json");
testThemAll("xpath-object.json");
// prints:
// path/to/xml/tag
// [path/to/xml/tag1, tag2, path/tag3]
// {path/to/xml={setting1?=true, setting2?=true}, path/to/tag3=null, path/tag2={setting1?=false, setting2=true}}
}
private void testThemAll(String fileName) throws IOException {
ObjectMapper om = new ObjectMapper();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
JsonNode jsonNode = om.readTree(inputStream).get("xpath");
if (jsonNode.isValueNode())
System.out.println(jsonNode.asText());
else if (jsonNode.isArray()) {
System.out.println(om.readValue(jsonNode.toString(), List.class));
} else if (jsonNode.isObject()) {
Map<String, Map<String, Boolean>> value = om.readValue(jsonNode.toString(), Map.class);
System.out.println(value);
}
}
I have a JSON which looks like,
{
"person": {
"name":"Sam",
"surname":"ngonma"
},
"car": {
"make":"toyota",
"model":"yaris"
}
}
I am writing this to Amazon SQS with the below lines,
ObjectMapper mObjectMapper = new ObjectMapper();
sqsExtended.sendMessage(new SendMessageRequest(awsSQSUrl, mObjectMapper.writeValueAsString(claim)));
I have a separate array of values, if the JSON has its value in that array I have to write the field as null.
If my string array is ["Sam", "Toyota"] my final JSON should look like this,
{
"person": {
"name":null,
"surname":"ngonma"
},
"car": {
"make":null,
"model":"yaris"
}
}
The string array is externalized. It may have additional values in future too. Could someone suggest me a good link or idea to address this ?
The most flexible way I could come up with is to use Jackson's JsonAnyGetter annotation. It allows you to provide Jackson with a Map representation of the state of your pojo. filtering values from a Map can be done in iterative way. filtering values from a Map that contains Maps can be done in recursive way.
Here is a solution I built from provided question
public class Claim {
Map<String, Object> properties = new HashMap<>();
public Claim() {
// may be populated from instance variables
Map<String, String> person = new HashMap<>();
person.put("name", "Sam");
person.put("surname", "ngonma");
properties.put("person", person);
Map<String, String> car = new HashMap<>();
car.put("make", "Toyota");
car.put("model", "yaris");
properties.put("car", car);
}
// nullify map values based on provided array
public void filterProperties (String[] nullifyValues) {
filterProperties(properties, nullifyValues);
}
// nullify map values of provided map based on provided array
#SuppressWarnings("unchecked")
private void filterProperties (Map<String, Object> properties, String[] nullifyValues) {
// iterate all String-typed values
// if value found in array arg, nullify it
// (we iterate on keys so that we can put a new value)
properties.keySet().stream()
.filter(key -> properties.get(key) instanceof String)
.filter(key -> Arrays.asList(nullifyValues).contains(properties.get(key)))
.forEach(key -> properties.put(key, null));
// iterate all Map-typed values
// call this method on value
properties.values().stream()
.filter(value -> value instanceof Map)
.forEach(value -> filterProperties((Map<String, Object>)value, nullifyValues));
}
// provide jackson with Map of all properties
#JsonAnyGetter
public Map<String, Object> getProperties() {
return properties;
}
}
test method
public static void main(String[] args) {
try {
ObjectMapper mapper = new ObjectMapper();
Claim claim = new Claim();
claim.filterProperties(new String[]{"Sam", "Toyota"});
System.out.println(mapper.writeValueAsString(claim));
} catch (Exception e) {
e.printStackTrace();
}
}
output
{"car":{"model":"yaris","make":null},"person":{"surname":"ngonma","name":null}}
I have some class in Java that I want to convert to a Map<String, String>. The catch is that any fields of my java class that don't have an obvious String representation (collections, other classes) should be converted to json strings.
Here's an example:
#Data
#AllArgsConstructor
class MyClass {
String field1;
Long field2;
Set<String> field3;
OtherClass field4;
}
#Data
#AllArgsConstructor
class OtherClass {
String field1;
String field2;
}
ObjectMapper mapper = new ObjectMapper();
MyClass myClass = new MyClass("value",
123L,
Sets.newHashSet("item1", "item2"),
new OtherClass("value1", "value2"));
Map<String, String> converted =
mapper.convertValue(myClass, new TypeReference<Map<String, String>>(){});
At this point, converted should look like the following:
"field1" -> "value"
"field2" -> "123"
"field3" -> "[\"item1\", \"item2\"]"
"field4" -> "{\"field1\":\"value1\",\"field2\":\"value2\"}"
Instead, the call to mapper.convertValue fails when trying to deserizlize the Set with the exception java.lang.IllegalArgumentException: Can not deserialize instance of java.lang.String out of START_ARRAY token.
Are there any special configurations I can annotate MyClass with or ways to configure the ObjectMapper to make this work the way I want it to?
Here's one way to do it.
private static final ObjectMapper mapper = new ObjectMapper();
public Map<String, String> toMap(Object obj) {
// Convert the object to an intermediate form (map of strings to JSON nodes)
Map<String, JsonNode> intermediateMap = mapper.convertValue(obj, new TypeReference<Map<String, JsonNode>>() {});
// Convert the json nodes to strings
Map<String, String> finalMap = new HashMap<>(intermediateMap.size() + 1); // Start out big enough to prevent resizing
for (Map.Entry<String, JsonNode> e : intermediateMap.entrySet()) {
String key = e.getKey();
JsonNode val = e.getValue();
// Get the text value of textual nodes, and convert non-textual nodes to JSON strings
String stringVal = val.isTextual() ? val.textValue() : val.toString();
finalMap.put(key, stringVal);
}
return finalMap;
}
And if you want to convert the Map<String, String> back to the original class...
public static <T> T fromMap(Map<String, String> map, Class<T> clazz) throws IOException {
// Convert the data to a map of strings to JSON nodes
Map<String, JsonNode> intermediateMap = new HashMap<>(map.size() + 1); // Start out big enough to prevent resizing
for (Map.Entry<String, String> e : map.entrySet()) {
String key = e.getKey();
String val = e.getValue();
// Convert the value to the right type of JsonNode
JsonNode jsonVal;
if (val.startsWith("{") || val.startsWith("[") || "null".equals(val)) {
jsonVal = mapper.readValue(val, JsonNode.class);
} else {
jsonVal = mapper.convertValue(val, JsonNode.class);
}
intermediateMap.put(key, jsonVal);
}
// Convert the intermediate map to an object
T result = mapper.convertValue(intermediateMap, clazz);
return result;
}