How to parse a bson.Document - java

I have a java code that retrieves a bson.Document from MongoDB.
An example JSON/BSON document is:
{
"field1": "text1",
"field2": {
"field2Sub1": "text2"
}
}
In my java code I manipulate it like this to get values of field1 and field2Sub1
com.fasterxml.jackson.databind.ObjectMapper objectMapper;
org.bson.Document documentFromMongo = this.getDocumentFromMongo();
org.bson.Document field2Document = documentFromMongo.get("field2", Document.class);
String field1Value = objectMapper.convertValue(documentFromMongo.get("field1"), String.class);
String field2Sub1Value = objectMapper.convertValue(field2Document.get("field2Sub1"), String.class);
Is there any way or some library or method I can use to get the value of field2Sub1 like this way:
String field1Value = objectMapper.convertValue(documentFromMongo.get("field2.field2Sub1"), String.class);

I don't know of another library doing this, but you could use org.bson.Document provided functionality to achieve similar effect. It's quite simple
Split your input/path by dot
Use Document.getEmbedded()
Like so:
public class MongoMain {
private static final String JSON = "{\n" +
" \"field1\": \"text1\",\n" +
" \"field2\": {\n" +
" \"field2Sub1\": \"text2\"\n" +
" }\n" +
"}";
public static void main(String[] args) {
Document document = Document.parse(JSON);
String path = "field2.field2Sub1";
String value = extractValue(document, path);
System.out.println("extracted value - " + value);
}
private static String extractValue(Document document, String dotNotationPath) {
List<String> path = Arrays.asList(dotNotationPath.split("\\."));
return document.getEmbedded(path, String.class);
}
}
The extractValue method does the trick - first split by dot and build a list, because getEmbedded accepts a list, then getEmbedded handles the rest.

Related

How to Serialize Map<String,String> to an Array of Key:Values

How do you serialize a map of keys-value pairs such that that the resulting JSON has the form:
["key1":"value1", "key2":"value2", "keyN":"valueN"]
The default way that a Map is serialized is in the form:
{"key1":"value1", "key2":"value2", "keyN":"valueN"}
The custom serializer that I tried to make seems to want to put them in separate objects as when I take the comments out of the below snippet
public class MapToArraySerializer extends JsonSerializer<Map<String,String>>
{
#Override
public void serialize(Map<String,String> additionalData, JsonGenerator generator, SerializerProvider provider) throws IOException
{
generator.writeStartArray();
for (String key : additionalData.keySet()) {
//generator.writeStartObject();
generator.writeObjectField(key, additionalData.get(key));
//generator.writeEndObject();
}
generator.writeEndArray();
}
}
I get the following:
com.fasterxml.jackson.core.JsonGenerationException: Can not write a field name, expecting a value
at com.fasterxml.jackson.core.JsonGenerator._reportError(JsonGenerator.java:1886)
at com.fasterxml.jackson.core.json.WriterBasedJsonGenerator.writeFieldName(WriterBasedJsonGenerator.java:126)
at com.fasterxml.jackson.core.JsonGenerator.writeObjectField(JsonGenerator.java:1651)
As #Henrik pointed out, this is not valid but I was able to accomplish this by using the #JsonRawValue annotation:
#JsonRawValue
public String getAdditionalData()
{
String json = "[";
List<String> keys = new ArrayList<>(additionalData.keySet());
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
json = json + "\"" + key + "\":\"" + additionalData.get(key) + "\"";
if (i + 1 < keys.size()) {
json = json + ",";
}
}
return json + "]";
}

How to merge two json Strings into one in java

If we have given 2 Strings of type json, how can we merge them into single json String in java?
e.g.
String json1 = {
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S"
}
}
}
String json2 = {
"glossary": {
"title": "person name",
"age": "25"
}
}
Should produce
String mergedJson = {
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S"
},
"age": "25"
}
}
Below code should do it, with a couple of assumptions:
You are using ObjectMapper of Jackson library (com.fasterxml.jackson.databind.ObjectMapper) to serialise/deserialise json
fields of json1 will always overwrite json2 while merging
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map1 = mapper.readValue("json1", Map.class);
Map<String, Object> map2 = mapper.readValue("json2", Map.class);
Map<String, Object> merged = new HashMap<String, Object>(map2);
merged.putAll(map1);
System.out.println(mapper.writeValueAsString(merged));
Here is the code which recursively merges two jsons. This outputs as excepted:
NOTE: This is deep merge, not shallow merge ( similar concept used for shall vs deep copy)
private static JsonObject merge(JsonObject json1Obj, JsonObject json2Obj) {
Set<Entry<String, JsonElement>> entrySet1 = json1Obj.entrySet();
for (Entry<String, JsonElement> entry : entrySet1) {
String key1 = entry.getKey();
if (json2Obj.get(key1) != null) {
JsonElement tempEle2 = json2Obj.get(key1);
JsonElement tempEle1 = entry.getValue();
if (tempEle2.isJsonObject() && tempEle1.isJsonObject()) {
JsonObject mergedObj = merge(tempEle1.getAsJsonObject(),
tempEle2.getAsJsonObject());
entry.setValue(mergedObj);
}
}
}
Set<Entry<String, JsonElement>> entrySet2 = json2Obj.entrySet();
for (Entry<String, JsonElement> entry : entrySet2) {
String key2 = entry.getKey();
if (json1Obj.get(key2) == null) {
json1Obj.add(key2, entry.getValue());
}
}
return json1Obj;
}
Consider using a library that does this job for you, like JSON Merge, available at Maven Central.
You will get the desired result with a single line of code (you may ignore the String declarations if you already have the JSONObjects previously loaded):
String json1 = "{\n"
+ " \"glossary\": {\n"
+ " \"title\": \"example glossary\",\n"
+ " \"GlossDiv\": {\n"
+ " \"title\": \"S\"\n"
+ " }\n"
+ " }\n"
+ " }";
String json2 = "{\n"
+ " \"glossary\": {\n"
+ " \"title\": \"person name\",\n"
+ " \"age\": \"25\"\n"
+ " }\n"
+ " }";
JSONObject result = new JsonMerger<>(JSONObject.class).merge(json2, json1);
Note: the first JSON parameter passed to the merge method will always have more precedence/importance than the second one in case of key collisions.
This library works with Jackson, Gson, and other JSON providers as well.
So I'm quite late to the party but I wanted to share my solution if anybody stumbles across this.
You can deeply merge two json strings with com.fasterxml.jackson.core:jackson-databind ObjectMapper.readerForUpdating().
In this scenario you pass in two Json as String and merge them via readerForUpdating (untested code):
public String mergeJsonStrings(String json1, String json2) {
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.readerForUpdating(json1);
String result = reader.readValue(json2);
return result;
}
I used similar code to merge a property into an existing dataset. In this example the SomeProperties class contains a hashmap which holds the properties for a specific user. The passed in propertiesString is a single dot separated property e.g. some.random.property=value. The property will be transformed into a JsonNode with com.fasterxml.jackson.dataformat:jackson-dataformat-properties.
public SomeProperties mergeProperties(SomeProperties someProperties, String propertiesString) {
JavaPropsMapper javaPropsMapper = new JavaPropsMapper();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = javaPropsMapper.readTree(propertiesString);
ObjectReader objectReader = mapper.readerForUpdating(someProperties.getProperties());
HashMap<String, Object> mergedProperties = objectReader.readValue(jsonNode);
someProperties.setProperties(mergedProperties);
return someProperties;
}
In both cases everything passed into objectReader.readValue() will override existing keys.

Using Jackson to manually parse JSON

Is it possible to use Jackson library to manually parse JSON?
I.e. I don't want to use ObjectMapper and convert JSON to some object, but rather I want select some individual properties from JSON, like in XPath:
For example this is my JSON:
{
"person": {
"name": "Eric",
"surname": "Ericsson",
"address" {
"city": "LA",
"street": "..."
}
}
}
And all what I want is just to get Name and the City, for this cases I don't want introduce 2 new Java classes (Person and Address) and use them with ObjectMapper, but I'm just want to read this values like in xPath:
Pseudocode:
String name = myJson.get("person").get("name")
String city = myJson.get("person").get("address").get("city")
You can use the Jackson tree model and JsonNode#at(...) method which takes the Json Pointer expression as a parameter.
Here is an example:
public class JacksonJsonPointer {
static final String JSON = "{"
+ " \"person\": {"
+ " \"name\": \"Eric\","
+ " \"surname\": \"Ericsson\","
+ " \"address\": {"
+ " \"city\": \"LA\","
+ " \"street\": \"...\""
+ " }"
+ " }"
+ "}";
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final JsonNode json = mapper.readTree(JSON);
System.out.println(json.at("/person/name"));
System.out.println(json.at("/person/address/city"));
}
}
Output:
"Eric"
"LA"
Yes Using Json parser you can parse your Json, Below is a sample example you can find more in jackson documentation
JsonParser jsonParser = new JsonFactory().createJsonParser(jsonStr);
while(jsonParser.nextToken() != JsonToken.END_OBJECT){
String name = jsonParser.getCurrentName();
if("name".equals(name)) {
jsonParser.nextToken();
System.out.println(jsonParser.getText());
}
if("surname".equals(name)) {
jsonParser.nextToken();
System.out.println(jsonParser.getText());
}
if("city".equals(name)) {
jsonParser.nextToken();
System.out.println(jsonParser.getText());
}
}

Json parser converting null values to 0

I am using net.sf.json library and using it to parse my refMap like:
Map<String, Group> myMap = new HashMap<String,Group>();
myMap = this.getGroupValues();
JSONObject jsonObj = new JSONObject();
jsonObj.putAll(refMap);
File jsonFile = new File("./TempJson.txt");
FileWriter writer = new FileWriter(jsonFile);
fileWriter.write(jsonObj.toString());
where my Group class is defined as:
class Group {
Double val;
Integer num;
Section sectionObj;
//..getters & setters
}
The Problem is this.getGroupValues() returns some Group objects where val/num (wrapper classes) values are 'null' and then JsonObject parser converts it to 0 like: "val":0,"num":0
while if sectionObj is null , parser keeps it as null as "sectionObj":null
How to get null values for wrapper class Objects too in json file ?
I would suggest using a different JSON library. For example, GSON will leave null values out of the serialized JSON text. When it is deserialized by GSON, those missing values are set to null in the new object.
Consider this example:
public class Main {
public static void main(String[] args) {
Gson gson = new Gson();
final String json = gson.toJson(new Group());
System.out.println(json);
Group g = gson.fromJson(json, Group.class);
System.out.println(g);
}
public static class Group {
Double val;
Integer num;
Section sectionObj;
// Getters and setters...
#Override
public String toString() {
return "val: '" + val + "' num: '" + num
+ "' sectionObj: '" + sectionObj + "'";
}
}
public static class Section {}
}
which outputs the following:
{}
val: 'null' num: 'null' sectionObj: 'null'

Deserialization of sometimes string and sometimes object with Gson

I need to parse this type of JSON data to java objects:
{"id": 1, "blob": "example text"}
{"id": 2, "blob": {"to": 1234, "from": 4321, "name": "My_Name"}}
I am using Gson, and don't know how to get around this particular problem, of "blob" sometimes being a string and sometimes an object.
One solution to your problem is to write a TypeAdapter for your class, however if you have only cases like that in your example, you can achieve the same result letting Gson do the job for you using the most generic class you can for deserialization.
What I mean is shown in the below code.
package stackoverflow.questions.q19478087;
import com.google.gson.Gson;
public class Q19478087 {
public class Test {
public int id;
public Object blob;
#Override
public String toString() {
return "Test [id=" + id + ", blob=" + blob + "]";
}
}
public static void main(String[] str){
String json1 = "{\"id\": 1, \"blob\": \"example text\"}";
String json2 = "{\"id\": 2, \"blob\": {\"to\": 1234, \"from\": 4321, \"name\": \"My_Name\"}}";
Gson g = new Gson();
Test test1 = g.fromJson(json1, Test.class);
System.out.println("Test 1: "+ test1);
Test test2 = g.fromJson(json2, Test.class);
System.out.println("Test 2: "+ test2);
}
}
and this is my execution:
Test 1: Test [id=1, blob=example text]
Test 2: Test [id=2, blob={to=1234.0, from=4321.0, name=My_Name}]
In second case, blob will be deserialized as a LinkedTreeMap, so you can access its elements using ((Map) test2.blob).get("to") for example;
Let me know if it's enough or if you are interested also in the type adapter solution.
Try this one
Your POJO
class FromToName{
String to;
String from;
String name;
#Override
public String toString() {
return "FromToName [to=" + to + ", from=" + from + ", name=" + name
+ "]";
}
}
Your conversion code
String json ="{\"id\": 1, \"blob\": \"example text\"}";
//String json = "{\"id\": 2, \"blob\": {\"to\": 1234, \"from\": 4321, \"name\": \"My_Name\"}}";
Gson gson = new Gson();
JsonElement element = gson.fromJson (json, JsonElement.class);
JsonObject jsonObj = element.getAsJsonObject();
JsonElement id = jsonObj.get("id");
System.out.println(id);
if(jsonObj.get("blob") instanceof JsonPrimitive ){
JsonElement blob = jsonObj.get("blob");
System.out.println(blob);
}else{
FromToName blob = gson.fromJson (jsonObj.get("blob"), FromToName.class);
System.out.println(blob);
}
If you have any doubt in this let me know
Take that as a JSON Element and then use isMethods() to figure out the type at runtime.
Documentation
JsonParser jp = new JsonParser();
JsonElement ele = jp.parse(jsonString).getAsJsonObject().get("blob");;
if (ele.isJsonObject()) {
//do related stuff here
} else if (ele.isJsonArray()) {
//do related stuff here
}

Categories

Resources