I have a custom map implementation called ObjectMap as follows:
public class ObjectMap extends LinkedHashMap<String, Object> implements Serializable {
...
}
I can convert any POJO to this ObjectMap using jackson ObjectMapper as follows:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(
objectMapper.getSerializationConfig().
getDefaultVisibilityChecker().
withFieldVisibility(JsonAutoDetect.Visibility.ANY).
withGetterVisibility(JsonAutoDetect.Visibility.NONE).
withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)
);
ObjectMap objectMap = objectMapper.convertValue(object, new TypeReference<ObjectMap>() {});
But the problem is if my POJO's complex fields are being mapped to a LinkedHashMap not ObjectMap. So how do I enforce ObjectMapper to map internal fields also into a ObjectMap instead of LinkedHashMap?
I have come up with below solution at last:
public class Document extends LinkedHashMap<String, Object> implements Serializable {
}
public class JacksonMapper {
private ObjectMapper objectMapper;
public <T> Document asDocument(T object) {
ObjectMapper objectMapper = getObjectMapper();
JsonNode node = objectMapper.convertValue(object, JsonNode.class);
return loadDocument(node);
}
protected ObjectMapper getObjectMapper() {
if (objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper.setVisibility(
objectMapper.getSerializationConfig().
getDefaultVisibilityChecker().
withFieldVisibility(JsonAutoDetect.Visibility.ANY).
withGetterVisibility(JsonAutoDetect.Visibility.NONE).
withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)
);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
}
return objectMapper;
}
private Document loadDocument(JsonNode node) {
Document document = new Document();
Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> entry = fields.next();
String name = entry.getKey();
JsonNode value = entry.getValue();
document.put(name, loadObject(value));
}
return document;
}
private Object loadObject(JsonNode node) {
if (node == null) return null;
try {
switch (node.getNodeType()) {
case ARRAY:
return loadArray(node);
case BINARY:
return node.binaryValue();
case BOOLEAN:
return node.booleanValue();
case MISSING:
case NULL:
return null;
case NUMBER:
return node.numberValue();
case OBJECT:
return loadDocument(node);
case POJO:
return loadDocument(node);
case STRING:
return node.textValue();
}
} catch (IOException e) {
return null;
}
return null;
}
private List loadArray(JsonNode array) {
if (array.isArray()) {
List list = new ArrayList();
Iterator iterator = array.elements();
while (iterator.hasNext()) {
Object element = iterator.next();
if (element instanceof JsonNode) {
list.add(loadObject((JsonNode) element));
} else {
list.add(element);
}
}
return list;
}
return null;
}
}
Hope this will help someone someday.
Related
I need to create new T object based on attribute map then I want to somehow feel this object with oneResult values
What I tried so far:
while (set.next()) {
list.add(helper.creteObject(set));
}
public <T> T creteObject(ResultSet oneResult) throws SQLException {
Set<String> setOfFields = classFieldName.get(aClass.getSimpleName());
Map<String, ColumnType> fieldFieldType = classFieldFiledType.get(aClass.getSimpleName()); //Map attribute attributeType
ObjectMapper objectMapper = new ObjectMapper();
T pojo = objectMapper.convertValue(fieldFieldType, new TypeReference<T>() {
});
for (Field declaredField : pojo.getClass().getDeclaredFields()) {
ColumnType columnType = getColumnType(declaredField.getType());
switch (columnType) {
case INT:
int anInt = oneResult.getInt(declaredField.getName());
break;
case STRING:
String string = oneResult.getString(declaredField.getName());
}
}
}
Now how to set this result to pojoobject
Im having trouble to deserialize the object back into the original class. The problem is that the Component class have to be serialized in that spezific way with the features beeing a seperate list/set object.The attributes have to be seperate key/value pairs. If i try to deserialize the JSON/object back, i get an "canĀ“t construct instance of LinkedHashMap" error.
Here is the class to parse to JSON
public class Component {
private Set<Attribute> attributes;
private Set<Feature> features;
}
public class Attribute {
private String type;
private String key;
private String value;
}
public class Feature {
private String key;
private String value;
}
Here is the serializer
public class ComponentSerializer extends StdSerializer<Component> {
public ComponentSerializer(){
this(null);
}
public ComponentSerializer(Class<Component> c){
super(c);
}
#Override
public void serialize(Component com, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
for(Attribute att : com.getAttributes()){
gen.writeStringField(att.getKey(),att.getValue());
}
Set<Feature> features = new HashSet<Feature>();
for(Feature ftr : com.getFeatures()){
features.add(new Feature(ftr.getKey(), ftr.getValue()));
}
gen.writeObjectField("features", features);
gen.writeEndObject();
}
}
The outcome JSON is something like
{
"Foo": "Fooo",
"Bar": "Barr",
...
"features": [
{
"key": "foo",
"value": "bar"
},
{
"key": "bar",
"value": "foor"
}
...
]
}
My attempt on deserialization
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(p.getCodec().readTree(p).toString(), new TypeReference<Map<String, Object>>() {
});
Set<Attribute> attributes = new HashSet<>();
Set<Feature> features = new HashSet<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals("features")){
//Here i cant create the Set<Features> from the object without the exception
continue;
}
attributes.add(new Attribute("", entry.getKey(), entry.getValue().toString()));
}
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(p.getCodec().readTree(p).toString(), new TypeReference<Map<String, Object>>() {});
Set<Attribute> attributes = new HashSet<>();
Set<Feature> features = new HashSet<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals("features")) {
List<LinkedHashMap> featureList = (List<LinkedHashMap>) entry.getValue();
featureList.stream().forEach(feature -> {
features.add(new Feature(feature.get("key").toString(), feature.get("value").toString()));
});
continue;
}
attributes.add(new Attribute("", entry.getKey(), entry.getValue().toString()));
}
How can I convert a json string like
{
"a.b": 1,
"a.c.d": 2
}
into json string
{
"a": {
"b": 1,
"c": {
"d": 2
}
}
}
by using ObjectMapper?
The easiest option is to create custom deserializer or custom been with #JsonAnySetter and #JsonAnyGetter Here is example:
public static final String json = "{\"a.b\": 1,\"a.c.d\": 2}";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
HelperBean bean = mapper.readValue(json, HelperBean.class);
System.out.println(mapper.writeValueAsString(bean));
// result: {"a":{"b":1,"c":{"d":2}}}
}
public static class HelperBean{
#JsonIgnore
private Map<String, Object> data = new TreeMap<>();
#JsonAnySetter
public void setDays(String key, Object value){
String[] parts = key.split("\\.");
Map<String, Object> currMap = data;
for (int i = 0; i< parts.length; i++){
String part = parts[i];
Object subMap = currMap.get(part);
if (i == parts.length - 1) // last node
currMap.put(part, value);
else if(subMap == null){ // new node
subMap = new TreeMap<>();
currMap.put(part, subMap);
currMap = (Map<String, Object>) subMap;
}else if (subMap instanceof Map){ // existing node
currMap.put(part, subMap);
currMap = (Map<String, Object>) subMap;
} else { // node conflict
// handle exception when a.b = 1 and a.b.c = 1
}
}
}
#JsonAnyGetter
public Map<String, Object> getData() {
return data;
}
}
Here's the problem:
public static <T> T execute(String query, Function<List<JsonNode>, T> function) {
String jsonString = HttpRequest.get("http://overpass-api.de/api/interpreter", true, "data", query).body();
List<JsonNode> list = toJsonNodeList(jsonString);
T res = function.apply(list);
return res;
}
This method:
perform a query that returns a json string
transform that string into a JsonNode list
finally, transform each JsonNode into a particular object
This is an example of function that convert each JsonNode into a geojson geometry and returns a geojson result set:
public class GeojsonMapper implements Function<List<JsonNode>, GeojsonSingleListResultSet> {
#Override
public GeojsonSingleListResultSet apply(List<JsonNode> list) {
List<Element> elementList = list.parallelStream()
.map(jsonNode -> {
String id = jsonNode.get("id").asText();
JsonNode tags = jsonNode.get("tags");
switch (jsonNode.get("type").asText()) {
case "node":
return new Point(jsonNode.get("lat").asDouble(), jsonNode.get("lon").asDouble(), id, tags);
case "way":
ArrayList<Point> points = new ArrayList<>();
JsonNode nodeList = jsonNode.get("geometry");
for (int j = 0; j < nodeList.size(); j++) {
JsonNode wayNode = nodeList.get(j);
points.add(j, new Point(wayNode.get("lat").asDouble(), wayNode.get("lon").asDouble()));
}
if (Polygon.isPolygon(points, tags)) {
return new Polygon(points, id, tags);
} else {
return new LineString(points, id, tags);
}
default:
Iterator<JsonNode> iterator = jsonNode.get("members").getElements();
List<List<Point>> rings = new ArrayList<>();
List<Point> ring = null;
while (iterator.hasNext()) {
JsonNode member = iterator.next();
JsonNode geometry = member.get("geometry");
ring = new ArrayList<>();
for (int ringIndex = 0; ringIndex < geometry.size(); ringIndex++) {
JsonNode coordinates = geometry.get(ringIndex);
ring.add(new Point(coordinates.get("lat").asDouble(), coordinates.get("lon").asDouble()));
}
rings.add(ring);
}
return new Multipolygon(Polygon.buildPolygons(rings), id, tags);
}
})
.collect(toList());
return new GeojsonSingleListResultSet(elementList);
}
}
Everything works well, but the toJsonNodeList() method is very slow in comparison to the method function.apply() that use a Stream. This is the toJsonNodeList()'s code:
private static List<JsonNode> toJsonNodeList(String s){
ObjectMapper mapper = new ObjectMapper();
List<JsonNode> list = new ArrayList<>();
try{
JsonNode resultSet = mapper.readTree(s).get("elements");
Iterator<JsonNode> iterator = resultSet.getElements();
while (iterator.hasNext()) {
list.add(iterator.next());
}
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
Is there a way to parse a json string using a parallelStream so as to extract each element and convert it to a JsonNode?
What's the best way to deserialize this Json object?
{
"key1" : "val1",
"key2" : "blank"
}
into a java hashmap, where the string blank is replaced with null?
{
"key1" : "val1",
"key2" : null
}
I am currently using Jackson for deserialization.
You will find part of the answer here.
You just need to manipulate the line inside the while loop:
Object value;
if (object.get(key).equals("blank")) {
value = "null";
} else {
value = object.get(key);
}
and make print out will give:
System.out.println(map.get("key1")); // returns val1
System.out.println(map.get("key2")); // returns null
You final code will look like this, and you might need to import the proper .jar files:
import com.orsoncharts.util.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import static com.sun.xml.internal.ws.binding.WebServiceFeatureList.toList;
public class JsonAnswerOne {
public static void main(String[] args) throws JSONException {
String input = "{\n" +
" \"key1\" : \"val1\",\n" +
" \"key2\" : \"blank\"\n" +
"}";
parse(input);
}
private static void parse(String input) throws JSONException {
JSONObject mainObject = new JSONObject(input);
Map<String, Object> map = jsonToMap(mainObject);
System.out.println(map.get("key1")); // returns val1
System.out.println(map.get("key2")); // returns null
}
private static Map<String, Object> jsonToMap(JSONObject json) throws JSONException {
Map<String, Object> retMap = new HashMap<String, Object>();
if (json != JSONObject.NULL) {
retMap = toMap(json);
}
return retMap;
}
private static Map<String, Object> toMap(JSONObject object) throws JSONException {
Map<String, Object> map = new HashMap<String, Object>();
Iterator<String> keysItr = object.keys();
while (keysItr.hasNext()) {
String key = keysItr.next();
Object value;
if (object.get(key).equals("blank")) {
value = "null";
} else {
value = object.get(key);
}
if (value instanceof JSONArray) {
value = toList((JSONArray) value);
} else if (value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
map.put(key, value);
}
return map;
}
}
I tried this and ended up with this:
// use of the deserializer
String json = "{\"key1\":\"val1\",\"key2\":\"blank\"}";
ObjectMapper mapperMap = new ObjectMapper();
SimpleModule moduleMap = new SimpleModule();
moduleMap.addDeserializer(Map.class, new MapDeserializer());
mapperMap.registerModule(moduleMap);
Map map = mapperMap.readValue(json, Map.class);
// custom deserializer
public class MapDeserializer extends StdDeserializer<Map<String, String>> {
public MapDeserializer() {
this(null);
}
public MapDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Map<String, String> deserialize(JsonParser jp, DeserializationContext context)
throws IOException {
// definitely not the best way but it works...
Map<String, String> map = new HashMap<>();
String[] keys = new String[] {"key1", "key2"};
JsonNode node = jp.getCodec().readTree(jp);
String value;
for (String key : keys) {
value = node.get(key).asText();
if (value.equals("blank")) {
value = null;
}
map.put(key, value);
}
return map;
}
}
Full example solution with an additional example to deserialize the JSON into another class:
https://gist.github.com/audacus/e70ce0f3cd4b17197d911769e05b237e