How can I construct a json with arrays and objects in java - 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.

Related

Jackson deserialize Yaml Map when it is empty

I am trying to deserialize YAML map to Java Map of simple String keys and values for these cases:
myMap:
key: value
myMap:
# key: value
Here's the Java Pojo to create after deserialization:
class Config {
#JsonProperty Map<String, String> myMap;
public Config() {}
public Config(Map<String, String> myMap) { this.myMap = myMap; }
public Map<String, String> getMyMap() { return myMap; }
#Override
public String toString() {
return "Config{" + "map=" + myMap + '}';
}
}
And the main is :
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
String yaml = ".."
Config config = mapper.readValue(yaml, Config.class);
System.out.println(config);
}
The project uses these dependencies:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.1.4</version>
</dependency>
In case of yaml = "myMap:\n" + " key: somevalue", the deserialization works fine, and we can see Config{myMap={key=somevalue}}
In case of yaml = "myMap:\n" + ""; (empty or commented entries), Jackson logs these errors:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [map type; class java.util.LinkedHashMap, [simple type, class java.lang.String] -> [simple type, class java.lang.String]] from String value; no single-String constructor/factory method (through reference chain: com.example.Config["myMap"]) ...
How can I do to manage the case where the lines are commented or there are no provided key/value pairs?
A YAML block-style map, like the one you use, is implicitly started with the first key/value pair. Therefore, in absence of key/value pairs, no map is started – the value of myMap will be the empty scalar. An empty scalar is loaded as empty string by Jackson, which leads to the error.
To mitigate this, you need to explicitly set the value to the empty map:
myMap: {}
{} is a flow-style empty map.

How read value from properties file and put into JSON file

I have liquibase.json file. Threa are:
"runningAs": {
"username": "Programer200"
}
I try to creat custom property:
liquibaseuser=root
Is it possible put value of liquibaseuser to liquibase.json ?
You could parse the json to a model and change the values.
import com.fasterxml.jackson.databind.ObjectMapper;
public class App
{
public static void main(String[] args)
{
// 1 read property
String property = "root";
// 2 read json
String json = "{\"username\": \"Programer200\"}";
// 3 map json to model
ObjectMapper mapper = new ObjectMapper();
MyModel jsonObject = mapper.readValue(json, MyModel.class);
// 4 change variables
jsonObject.username = property;
System.out.println(mapper.writer().writeValueAsString(jsonObject));
}
}
class MyModel
{
public String username;
}
You can utilize the below library for the data access.
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency
After access the data you can modify the Json String. But firstly you require to get the data like below.
String user = JsonPath.read(json, "$.runningAs.username");
System.out.println(user);
In your quandary, it seem's like you require key and value, then you can utilize the map.

Java: Parsing JSON with unknown keys to Map

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

Jackson filtering out fields without annotations

I was trying to filter out certain fields from serialization via SimpleBeanPropertyFilter using the following (simplified) code:
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("test",
SimpleBeanPropertyFilter.filterOutAllExcept("data1"));
try {
String json = mapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1","data2":"value2"}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
private static class Data {
public String data1 = "value1";
public String data2 = "value2";
}
Us I use SimpleBeanPropertyFilter.filterOutAllExcept("data1")); I was expecting that the created serialized Json string contains only {"data1":"value1"}, however I get {"data1":"value1","data2":"value2"}.
How to create a temporary writer that respects the specified filter (the ObjectMapper can not be re-configured in my case).
Note: Because of the usage scenario in my application I can only accept answers that do not use Jackson annotations.
If for some reason MixIns does not suit you. You can try this approach:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
List<String> exclusions = Arrays.asList("field1", "field2");
return exclusions.contains(m.getName())|| super.hasIgnoreMarker(m);
}
});
You would normally annotate your Data class to have the filter applied:
#JsonFilter("test")
class Data {
You have specified that you can't use annotations on the class. You could use mix-ins to avoid annotating Data class.
#JsonFilter("test")
class DataMixIn {}
Mixins have to be specified on an ObjectMapper and you specify you don't want to reconfigure that. In such a case, you can always copy the ObjectMapper with its configuration and then modify the configuration of the copy. That will not affect the original ObjectMapper used elsewhere in your code. E.g.
ObjectMapper myMapper = mapper.copy();
myMapper.addMixIn(Data.class, DataMixIn.class);
And then write with the new ObjectMapper
String json = myMapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1"}
The example of excluding properties by name:
public Class User {
private String name = "abc";
private Integer age = 1;
//getters
}
#JsonFilter("dynamicFilter")
public class DynamicMixIn {
}
User user = new User();
String[] propertiesToExclude = {"name"};
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, DynamicMixIn.class);
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("dynamicFilter", SimpleBeanPropertyFilter.filterOutAllExcept(propertiesToExclude));
mapper.setFilterProvider(filterProvider);
mapper.writeValueAsString(user); // {"name":"abc"}
You can instead of DynamicMixIn create MixInByPropName
#JsonIgnoreProperties(value = {"age"})
public class MixInByPropName {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, MixInByPropName.class);
mapper.writeValueAsString(user); // {"name":"abc"}
Note: If you want exclude property only for User you can change parameter Object.class of method addMixIn to User.class
Excluding properties by type you can create MixInByType
#JsonIgnoreType
public class MixInByType {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Integer.class, MixInByType.class);
mapper.writeValueAsString(user); // {"name":"abc"}
It seems you have to add an annotation which indicts which filter to use when doing the serialization to the bean class if you want the filter to work:
#JsonFilter("test")
public class Data {
public String data1 = "value1";
public String data2 = "value2";
}
EDIT
The OP has just added a note that just take the answer that not using a bean animation, then if the field you want to export is very less amount, you can just retrieve that data and build a Map of List yourself, there seems no other way to do that.
Map<String, Object> map = new HashMap<String, Object>();
map.put("data1", obj.getData1());
...
// do the serilization on the map object just created.
If you want to exclude specific field and kept the most field, maybe you could do that with reflect. Following is a method I have written to transfer a bean to a map you could change the code to meet your own needs:
protected Map<String, Object> transBean2Map(Object beanObj){
if(beanObj == null){
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(beanObj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (!key.equals("class")
&& !key.endsWith("Entity")
&& !key.endsWith("Entities")
&& !key.endsWith("LazyInitializer")
&& !key.equals("handler")) {
Method getter = property.getReadMethod();
if(key.endsWith("List")){
Annotation[] annotations = getter.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof javax.persistence.OneToMany){
if(((javax.persistence.OneToMany)annotation).fetch().equals(FetchType.EAGER)){
List entityList = (List) getter.invoke(beanObj);
List<Map<String, Object>> dataList = new ArrayList<>();
for(Object childEntity: entityList){
dataList.add(transBean2Map(childEntity));
}
map.put(key,dataList);
}
}
}
continue;
}
Object value = getter.invoke(beanObj);
map.put(key, value);
}
}
} catch (Exception e) {
Logger.getAnonymousLogger().log(Level.SEVERE,"transBean2Map Error " + e);
}
return map;
}
But I recommend you to use Google Gson as the JSON deserializer/serializer And the main reason is I hate dealing with exception stuff, it just messed up with the coding style.
And it's pretty easy to satisfy your need with taking advantage of the version control annotation on the bean class like this:
#Since(GifMiaoMacro.GSON_SENSITIVE) //mark the field as sensitive data and will not export to JSON
private boolean firstFrameStored; // won't export this field to JSON.
You can define the Macro whether to export or hide the field like this:
public static final double GSON_SENSITIVE = 2.0f;
public static final double GSON_INSENSITIVE = 1.0f;
By default, Gson will export all field that not annotated by #Since So you don't have to do anything if you do not care about the field and it just exports the field.
And if some field you are not want to export to json, ie sensitive info just add an annotation to the field. And generate json string with this:
private static Gson gsonInsensitive = new GsonBuilder()
.registerTypeAdapter(ObjectId.class,new ObjectIdSerializer()) // you can omit this line and the following line if you are not using mongodb
.registerTypeAdapter(ObjectId.class, new ObjectIdDeserializer()) //you can omit this
.setVersion(GifMiaoMacro.GSON_INSENSITIVE)
.disableHtmlEscaping()
.create();
public static String toInsensitiveJson(Object o){
return gsonInsensitive.toJson(o);
}
Then just use this:
String jsonStr = StringUtils.toInsensitiveJson(yourObj);
Since Gson is stateless, it's fine to use a static method to do your job, I have tried a lot of JSON serialize/deserialize framework with Java, but found Gson to be the sharp one both performance and handily.

Desearilze JSON Java EE REST with Jackson

I have a Java EE REST service, and have the following JSON that I am POSTing here:
[
{"name":"identifier","value":"9d036307-efc1-4c84-a8fb-cf5b0bffca43"},
{"name":"form.field.decision","value":"Approved"},
{"name":"form.field.reason","value":"asdf"}
]
Here is the method, which works, but is ugly.
#POST
#Path("process-response")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Map<String, String> processResponse(String responseData) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper om = new ObjectMapper();
Map<String, String> retval = new HashMap<String, String>();
for(Object o : om.readValue(responseData, java.util.ArrayList.class)){
Map<String, String> m = (Map) o;
retval.put(m.get("name"), m.get("value"));
}
return retval;
}
Is there some way that I can write a class or something that gets annotated onto the method here to indicate I want to use it?
Something like:
public class StringToMapCustom() implmeents ???? {
...
}
Then, to the working method above, I would do something like:
#DecodeWith(StringToMapCustom.class)
#POST
...
Any ideas? I cannot find any docs like this.

Categories

Resources