Java: Parsing JSON with unknown keys to Map - java

In Java, I need to consume JSON (example below), with a series of arbitrary keys, and produce Map<String, String>. I'd like to use a standard, long term supported JSON library for the parsing. My research, however, shows that these libraries are setup for deserialization to Java classes, where you know the fields in advance. I need just to build Maps.
It's actually one step more complicated than that, because the arbitrary keys aren't the top level of JSON; they only occur as a sub-object for prefs. The rest is known and can fit in a pre-defined class.
{
"al" : { "type": "admin", "prefs" : { "arbitrary_key_a":"arbitary_value_a", "arbitrary_key_b":"arbitary_value_b"}},
"bert" : {"type": "user", "prefs" : { "arbitrary_key_x":"arbitary_value_x", "arbitrary_key_y":"arbitary_value_y"}},
...
}
In Java, I want to be able to take that String, and do something like:
people.get("al").get("prefs"); // Returns Map<String, String>
How can I do this? I'd like to use a standard well-supported parser, avoid exceptions, and keep things simple.
UPDATE
#kumensa has pointed out that this is harder than it looks. Being able to do:
people.get("al").getPrefs(); // Returns Map<String, String>
people.get("al").getType(); // Returns String
is just as good.
That should parse the JSON to something like:
public class Person {
public String type;
public HashMap<String, String> prefs;
}
// JSON parsed to:
HashMap<String, Person>

Having your Person class and using Gson, you can simply do:
final Map<String, Person> result = new Gson().fromJson(json, new TypeToken<Map<String, Person>>() {}.getType());
Then, retrieving prefs is achieved with people.get("al").getPrefs();.
But be careful: your json string is not valid. It shouldn't start with "people:".

public static <T> Map<String, T> readMap(String json) {
if (StringUtils.isEmpty(json))
return Collections.emptyMap();
ObjectReader reader = new ObjectMapper().readerFor(Map.class);
MappingIterator<Map<String, T>> it = reader.readValues(json);
if (it.hasNextValue()) {
Map<String, T> res = it.next();
return res.isEmpty() ? Collections.emptyMap() : res;
}
return Collections.emptyMap();
}
All you need to do next, it that check the type of the Object. If it is Map, then you have an object. Otherwise, this is a simple value.

You can use Jackson lib to achieve this.
Put the following in pom.xml.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
Refer the following snippet that demonstrates the same.
ObjectMapper mapper = new ObjectMapper();
HashMap<String, Object> people = mapper.readValue(jsonString, new TypeReference<HashMap>(){});
Now, it is deserialized as a Map;
Full example:
import java.io.IOException;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class testMain {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
String json = "{\"address\":\"3, 43, Cashier Layout, Tavarekere Main Road, 1st Stage, BTM Layout, Ambika Medical, 560029\",\"addressparts\":{\"apartment\":\"Cashier Layout\",\"area\":\"BTM Layout\",\"floor\":\"3\",\"house\":\"43\",\"landmark\":\"Ambika Medical\",\"pincode\":\"560029\",\"street\":\"Tavarekere Main Road\",\"subarea\":\"1st Stage\"}}";
ObjectMapper mapper = new ObjectMapper();
HashMap<String, Object> people = mapper.readValue(json, new TypeReference<HashMap>(){});
System.out.println(((HashMap<String, String>)people.get("addressparts")).get("apartment"));
}
}
Output: Cashier Layout

Related

mapstruct convert list to map

I am a very new to mapstruct. I am trying to convert List to Map, I've searched a lot online, I've got some solutions like its not yet implemented in mapstruct seems. I will be glad if someone could able to provide some alternative solution.
All I am looking to convert mapping as below:
#Mapping
Map<String, Object> toMap(List<MyObj>)
#Mapping
List<MyObj> toList(Map<String, Object>)
where MyObj as below:
class MyObj {
String key; //map key
String value; //map value
String field1;
}
In above, only use key and value fields from MyObj class. I've found one solution but below is converting some object to MAP, but using Jackson below:
#Mapper
public interface ModelMapper {
ObjectMapper OBJECT_MAPPER = new ObjectMapper();
default HashMap<String, Object> toMap(Object filter) {
TypeFactory typeFactory = OBJECT_MAPPER.getTypeFactory();
return OBJECT_MAPPER.convertValue(filter, typeFactory.constructMapType(Map.class, String.class, Object.class));
}
}
is there anyway now to implement using mapstruct?
Map struct doesn't have implicit conversion for your desired List to Map. You can have a custom mapping method as follows:
#Mapper
public interface FooMapper {
default Map<String, Foo> convertFooListToMap(List<Foo> foos) {
// custom logic using streams or however you like.
}
}
Other options include custom mapper implementations that you write and refer with something like #Mapper(uses=CustomMapper.class)

Serialization and Deserialization of a Map<Object, Object>

Maybe I'm only being stupid right now, but I've been struggling really hard.
I am writing a networking application that interacts with another library right now.
The messages that this library produces are in the form Map<Path, Object>. These messages now need to be serialized.
I do not know of what type these Object's are. They are only transferred between two objects that can handle them, but for this need to be serialized.
I however struggle to understand how to do it. I've tried with Gson already but don't unterstand how to solve it.
Code looks something like this:
public interface Path extends Serializable{}
public interface Network{
public Map<Path, Object> getSendMessages();
public void receiveMessage(Map<Path, Object> message);
}
public class Main {
public static void main(String[] args) {
Network nw1 = NetworkProvider.getNetwork();
Network nw2 = NetworkProvider.getNetwork();
//I don't know what these actually do with the messages.
while(true) {
Map<Path, Object> message = nw1.getSendMessages();
//__________What to do here?__________________
SerializedMessage serializedMessage = ....
Map<Path, Object> deserializedMessage = ....
//____________________________________________
nw2.receiveMessage(deserializedMessage);
}
}
}
}
You can easily serialize and deserialize an Object Map by using ObjectMapper from Jackson.
This is an example of serializing:
ObjectMapper mapper = new ObjectMapper();
String jsonResult = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(message);
For more information, check this link:
Map Serialization

How can I construct a json with arrays and objects in java

I need a json in the following structure to pass as request body to my service.
{
"i":[
{
"a":{
"o1":"str1";
"o2":234;
}
}
]
}
I can either pass a map like
<"i[0].a.o1", "str1">
<"i[0].a.02", 345>
or pass them as string like
"i[0].a.o1"="str1"&"i[0].a.02"=345
to construct the Json.
How can I convert the map or string input to the json structure above?
Should I use inner classes for 'I' and 'A' and just use GsonUtils.getString(I)?
I agree with the answer https://stackoverflow.com/a/45367328/2735286: the Jackson library provides very nice features for serializing, deserializing objects to and from JSON.
Here is some sample code which creates the map structure you proposed, serializes it and then deserializes and checks, if the deserialized structure is the same as the original one.
public class JacksonMapperTest {
private static ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) throws IOException {
Map<String, Object> root = provideMap(); // Create map
String json = convertToJson(root); // Convert to string
System.out.println(json); // Print json
Map<String, Object> rootClone = convertToMap(json); // Convert string to map
System.out.println(root.equals(rootClone)); // Check, if the original and deserialized objects are the same.
}
private static Map<String, Object> convertToMap(String json) throws IOException {
TypeReference<HashMap<String,Object>> typeRef
= new TypeReference<HashMap<String,Object>>() {};
return mapper.readValue(json, typeRef);
}
private static String convertToJson(Map<String, Object> root) throws JsonProcessingException {
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
}
private static Map<String, Object> provideMap() {
Map<String, Object> root = new HashMap<>();
List<Map<String, Object>> i = new ArrayList<>();
root.put("i", i);
Map<String, Object> element = new HashMap<>();
Map<String, Object> a = new HashMap<String, Object>() {
{
put("o1", "str1");
put("o2", 234);
}
};
i.add(element);
element.put("a", a);
return root;
}
}
If you run this code this is what you will see in the console:
{
"i" : [ {
"a" : {
"o1" : "str1",
"o2" : 234
}
} ]
}
true
If you want to used Jackson in your project, you can create a Maven project with these dependencies in the pom.xml file:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
For more infos on Jackson, check: https://github.com/FasterXML/jackson
perhaps you should try jackson api. believe me, it will make your life way easier.
using this api, you can guarantee that you have a valid json object. also, you can easily build your json nodes.

Using Jackson to deserialize into a Map

I have a JSON object with two attributes: "key" which is a string, and "value" which can be deserialized into a Java bean.
{ "key": "foo", "value": "bar" }
The question is, given a list of such objects, can I deserialize it into a Map?
[{"key": "foo1", "value": "bar1"}, {"key": "foo2", "value": "bar2"}] -> Map<String, String>
Currently using Jackson-databind 2.1
You can easily convert above JSON to List<Map<String, String>>:
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
CollectionType mapCollectionType = mapper.getTypeFactory().constructCollectionType(List.class, Map.class);
List<Map<String, String>> result = mapper.readValue(json, mapCollectionType);
System.out.println(result);
}
}
Above program prints:
[{key=foo1, value=bar1}, {key=foo2, value=bar2}]
Since your structure does not match, you have two basic options:
Use two-phase handling: use Jackson to bind into intermediate representation that does match (which could be JsonNode or List<Map<String,Object>>), and then do conversion to desired type with simple code
Write custom serializer and/or deserializer
Jackson does not support extensive structural transformations (there are some simple ones, like #JsonUnwrapped), so this kind of functionality is unlikely to be added to databind module. Although it could be added as an extension module, if these "smart Map" types of structures are commonly used (they seem to be, unfortunately).
Had the same problem and was surprised Jackson wasn't able to natively handle it. Solution I went with was to create a custom setter on the object I was trying to marshal into :
public class somePojo {
private Map<String, String> mapStuff;
...
public void SetMapStuff(List<Map<String, String> fromJackson){
mapStuff= new HashMap<>();
for (Map<String, String> pair : fromJackson) {
put(pair.get("key"), pair.get("value"));
}
}
}
Jackson is smart enough to find that setter to and can happily pass it the List.

How to serialize a Map of a Map with GSON?

I want to serialize my Example class below into JSON using GSON.
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.LinkedHashMap;
public class Example
{
private LinkedHashMap<String,Object> General;
private static final String VERSION="Version";
private static final String RANGE="Range";
private static final String START_TIME="Start_Time";
private static final String END_TIME="End_Time";
public Example() {
General = new LinkedHashMap<String,Object>();
General.put(VERSION, "0.1");
LinkedHashMap<String,String> Range = new LinkedHashMap<String, String>();
Range.put(START_TIME, "now");
Range.put(END_TIME, "never");
General.put(RANGE, Range);
}
public String toJSON() {
Gson gson = new GsonBuilder().serializeNulls().create();
return gson.toJson(this);
}
}
I expected to get the following output:
{"General":{"Version":"0.1","Range":{"Start_Time":"now","End_Time":"never"}}}
But calling the function toJSON() returns
{"General":{"Version":"0.1","Range":{}}}
It seems that GSON cannot serialize the Map Range inside the Map General. Is this a limitation of GSON or am I doing something wrong here?
The reason why Nishant's answer works is because Gson's default constructor enables all kind of stuff per default that you would otherwise have to manually enably using the GsonBuilder.
From the JavaDocs:
Constructs a Gson object with default configuration. The default configuration has the following settings:
The JSON generated by toJson methods is in compact representation. This means that all the unneeded white-space is removed. You can change this behavior with GsonBuilder.setPrettyPrinting().
The generated JSON omits all the fields that are null. Note that nulls in arrays are kept as is since an array is an ordered list. Moreover, if a field is not null, but its generated JSON is empty, the field is kept. You can configure Gson to serialize null values by setting GsonBuilder.serializeNulls().
Gson provides default serialization and deserialization for Enums, Map, java.net.URL, java.net.URI, java.util.Locale, java.util.Date, java.math.BigDecimal, and java.math.BigInteger classes. If you would prefer to change the default representation, you can do so by registering a type adapter through GsonBuilder.registerTypeAdapter(Type, Object).
The default Date format is same as java.text.DateFormat.DEFAULT. This format ignores the millisecond portion of the date during serialization. You can change this by invoking GsonBuilder.setDateFormat(int) or GsonBuilder.setDateFormat(String).
By default, Gson ignores the com.google.gson.annotations.Expose annotation. You can enable Gson to serialize/deserialize only those fields marked with this annotation through GsonBuilder.excludeFieldsWithoutExposeAnnotation().
By default, Gson ignores the com.google.gson.annotations.Since annotation. You can enable Gson to use this annotation through GsonBuilder.setVersion(double).
The default field naming policy for the output Json is same as in Java. So, a Java class field versionNumber will be output as "versionNumber#quot; in Json. The same rules are applied for mapping incoming Json to the Java classes. You can change this policy through GsonBuilder.setFieldNamingPolicy(FieldNamingPolicy).
By default, Gson excludes transient or static fields from consideration for serialization and deserialization. You can change this behavior through GsonBuilder.excludeFieldsWithModifiers(int).
OK, now I see what the problem is. The default Map serializer, as you expected, does not support nested maps. As you can see in this source snippet from DefaultTypeAdapters (especially if you step through with a debugger) the variable childGenericType is set to the type java.lang.Object for some mysterious reason, so the runtime type of the value is never analysed.
Two solutions, I guess:
Implement your own Map serializer / deserializer
Use a more complicated version of your method, something like this:
public String toJSON(){
final Gson gson = new Gson();
final JsonElement jsonTree = gson.toJsonTree(General, Map.class);
final JsonObject jsonObject = new JsonObject();
jsonObject.add("General", jsonTree);
return jsonObject.toString();
}
Try this:
Gson gson = new Gson();
System.out.println(gson.toJson(General));
Not sure if you're still looking for a solution, this works for me:
import java.util.LinkedHashMap;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Example {
// private static LinkedHashMap<String,Object> General;
private ImmutableMap General;
private static final String VERSION="Version";
private static final String RANGE="Range";
private static final String START_TIME="Start_Time";
private static final String END_TIME="End_Time";
public Example() {
LinkedHashMap<String,String> Range = new LinkedHashMap<String, String>();
Range.put(START_TIME, "now");
Range.put(END_TIME, "never");
// General.put(RANGE, Range);
General = ImmutableMap.of(VERSION, "0.1", RANGE, Range);
}
public String toJSON() {
// Gson gson = new GsonBuilder().serializeNulls().create();
Gson gson = new Gson();
return gson.toJson(this);
}
}
returns: {"General":{"Version":"0.1","Range":{"Start_Time":"now","End_Time":"never"}}}
Obviously you could use ImmutableMap.copyOf(your_hashmap)here instead
A simpler alternative would be to use Jackson instead of GSON, serialization of a nested map works out of the box:
LinkedHashMap<String, Object> general;
general = new LinkedHashMap<String, Object>();
general.put("Version", "0.1");
LinkedHashMap<String, String> Range = new LinkedHashMap<String, String>();
Range.put("Start_Time", "now");
Range.put("End_Time", "never");
general.put("Range", Range);
// Serialize the map to json using Jackson
ByteArrayOutputStream os = new ByteArrayOutputStream();
new org.codehaus.jackson.map.ObjectMapper().writer().writeValue(os,
general);
String json = os.toString();
os.close();
System.out.println(json);
Output:
{"Version":"0.1","Range":{"Start_Time":"now","End_Time":"never"}}

Categories

Resources