Issue with conversion of Map inside a Map into JSON in Java - java

I have a treemap in Java which has structure like this below:
public static Map<String, Map<String, Integer>> testMap = new TreeMap<String, Map<String, Integer>>();
public String getTestMapInJson(Map<String, Map<String, Integer>> testMap){
ObjectMapper mapper = new ObjectMapper();
String testMapInJson = mapper.writeValueAsString(testMap);
return testMapInJson;
}
Now, when I am trying to convert this map in JSON using Jackson API(snippet above), I am getting data like this and I want to populate this data in HTML tables.
[
{
"key": "1.1.1.1",
"value": {
"string1": 500,
"string2": 400
}
},
{
"key": "2.2.2.2",
"value": {
"string1": 500,
"string2": 400
}
}
]
HTML table output is like:
IP Address Value
1.1.1.1 [object Object]
2.2.2.2 [object Object]
I want it like this:
IP Address Value
1.1.1.1 String1 count1
String2 count2
2.2.2.2 String1 count1
String2 count2
The internal hashmap doesn't get converted exactly into JSON format. I want something so that the internal hashmap too gets converted into similar map like the outer map.
It's feasible for me to make changes on any side (client/server). What should I do?

var resp = [
{
"ip": "1.1.1.1",
"data": {
"string1": 500,
"string2": 400
}
},
{
"ip": "2.2.2.2",
"data": {
"string1": 500,
"string2": 400
}
}
]
To access the Object:
for(var obj : resp) {
$.each(obj, function(key,value){
alert(key + "==" + value);
if(key == "data") {
$.each(value, function(innerKey, innerVal) {
alert(innerKey + "===" + innerVal);
});
}
});
}
To design table:
var table = $("<table>");
//header
var header = $("<tr>").append($("<th>").text("IP Address")).append($("<th>").text("Value"));
for(var obj : resp) {
$.each(obj, function(key,value){
var isFirst = true;
if(key == "data") {
$.each(value, function(innerKey, innerVal) {
var tr;
if(isFirst) {
// ip address and value
tr = $("<tr>").append($("<td>").text(value)).append($("<td>").text(innerKey + " " + innerValue));
isFirst = false;
} else {
// blank and value
tr = $("<tr>").append($("<td>").text("")).append($("<td>").text(innerKey + " " + innerValue));
}
table.append(tr);
});
}
});
}

Related

How to flatten JSON using Gson

I need to flatten a JSON strings(nested) using Gson ,
Given:
{
"name": "Terry",
"addr": {
"city": "NY",
"postCode": 123
},
"friends": ["John", "Paul"],
"nestedObject": { "a": { "b": { "c": {"d": "nested property" } } } }
}
Expected:
{
"name" :"Terry",
"addr.city": "NY",
"addr.postCode" : 123,
"friends.0": "John",
"friends.1": "Paul",
"nestedObject.a.b.c.d": "nested property"
}
or
{
"name" :"Terry",
"addr.city": "NY",
"addr.postCode" : 123,
"friends[0]": "John"
"friends[1]": "Paul"
"nestedObject.a.b.c.d": "nested property"
}
Really new to Java and need guidance how to build it up ,
My starting point :
1.Parse the Json using JsonParser as TreeModel
https://www.tutorialspoint.com/gson/gson_tree_model.htm
just found that JsonParser is deprecated :(
https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/JsonParser.html
is there alternative ?
2.Recursive function to loop through the Json elements and check
if element is list/dict - case yes , recursive call with all child ,
case else - append current key and val to some map
real pseudo code:
public class Test{
public static void main(String[] args) {
String jsonString = "{\"brand\" : \"Toyota\", \"doors\" : 5}";
JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject();
// Iterate through main key and call FlatJson for each one
// append result to a map
//print result
FlatJson - for given element ,
extract all child key
public String FlatJson( JsonElement jsonElement) {
// If input is an array, iterate through each element
if (jsonElement.isJsonArray()) {
for (JsonElement jsonElement1 : jsonElement.getAsJsonArray()) {
// Extract Key Value and return
}
} else {
// If input is object, iterate through the keys
if (jsonElement.isJsonObject()) {
Set<Map.Entry<String, JsonElement>> entrySet = jsonElement
.getAsJsonObject().entrySet();
for (Map.Entry<String, JsonElement> entry : entrySet) {
// Extract Key Value and return
}
}
}
// If input is element, check whether it corresponds to the key
else {
// return sonElement.toString() (as
}
}
}
}

How to modify values of JsonObject

I want to add a new field to jsonObject and this new field's name will be based on a value of another field. To be clear, this an examples of what I want to achieve.
{
"values": [
{
"id": "1",
"properties": [
{
"stat": "memory",
"data": 8
},
{
"stat": "cpu",
"data": 4
}
]
},
{
"id": "2",
"properties": [
{
"stat": "status",
"data": "OK"
},
{
"stat": "cpu",
"data": 4
}
]
}
]
}
I want to add a new field to each json object that will have the value of field "stat" as name.
{
"values": [
{
"id": "1",
"properties": [
{
"stat": "memory",
"data": 8,
"memory": 8
},
{
"stat": "cpu",
"data": 4,
"cpu": 4
}
]
},
{
"id": "2",
"properties": [
{
"stat": "status",
"data": 0,
"status": 0
},
{
"stat": "cpu",
"data": 4,
"cpu": 4
}
]
}
]
}
I have tried to do the following with JsonPath library but for me it's an ugly solution as I will parse the json three times and I do some manual replacements.
val configuration = Configuration.builder().options(Option.DEFAULT_PATH_LEAF_TO_NULL, Option.ALWAYS_RETURN_LIST).build()
val jsonContext5 = JsonPath.using(configuration).parse(jsonStr)
val listData = jsonContext.read("$['values'][*]['properties'][*]['data']").toString
.replace("[", "").replace("]", "").split(",").toList
val listStat = jsonContext.read("$['values'][*]['properties'][*]['stat']").toString
.replace("[", "").replace("]", "")
.replace("\"", "").split(",").toList
// Replacing values of "stat" by values of "data"
jsonContext5.map("$['values'][*]['properties'][*]['stat']", new MapFunction() {
var count = - 1
override def map(currentValue: Any, configuration: Configuration): AnyRef = {
count += 1
listData(count)
}
})
// replace field stat by its value
for( count <- 0 to listStat.size - 1){
val path = s"['values'][*]['properties'][$count]"
jsonContext5.renameKey(path, "stat", s"${listStat(count)}")
}
This is the result obtained
{
"values": [
{
"id": "1",
"properties": [
{
"data": 8,
"memory": "8"
},
{
"data": 4,
"cpu": "4"
}
]
},
{
"id": "2",
"properties": [
{
"data": 0,
"memory": "0"
},
{
"data": 4,
"cpu": "4"
}
]
}
]
}
Is there any better method to achieve this result ? I tried to do it with gson but it's not good handling paths.
This a way to do it with Gson but I will lose the information about other columns since I'm creating another json.
val jsonArray = jsonObject.get("properties").getAsJsonArray
val iter = jsonArray.iterator()
val agreedJson = new JsonArray()
while(iter.hasNext) {
val json = iter.next().getAsJsonObject
agreedJson.add(replaceCols(json))
}
def replaceCols(json: JsonObject) = {
val fieldName = "stat"
if(json.has(fieldName)) {
val columnName = json.get(fieldName).getAsString
val value: String = if (json.has("data")) json.get("data").getAsString else ""
json.addProperty(columnName, value)
}
json
}
How about something like this?
private static void statDup(final JSONObject o) {
if (o.containsKey("properties")) {
final JSONArray a = (JSONArray) o.get("properties");
for (final Object e : a) {
final JSONObject p = (JSONObject) e;
p.put(p.get("stat"), p.get("data"));
}
} else {
for (final Object key : o.keySet()) {
final Object value = o.get(key);
if (value instanceof JSONArray) {
for (final Object e : (JSONArray) value) {
statDup((JSONObject) e);
}
}
}
}
}
Using Gson, what you should do is create a base class that represents your initial JSON object. Then, extend that class and add the additional attribute(s) you want to add, such as "stat". Then, load the JSON objects into memory, either one by one or all together, then make the necessary changes to each to encompass your changes. Then, map those changes to the new class if you didn't in the prior step, and serialize them to a file or some other storage.
This is type-safe, a pure FP circe implementation with circe-optics:
object CirceOptics extends App {
import cats.Applicative
import cats.implicits._
import io.circe.{Error => _, _}
import io.circe.syntax._
import io.circe.parser._
import io.circe.optics.JsonPath._
val jsonStr: String = ???
def getStat(json: Json): Either[Error, String] =
root.stat.string.getOption(json)
.toRight(new Error(s"Missing stat of string type in $json"))
def getData(json: Json): Either[Error, Json] =
root.data.json.getOption(json)
.toRight(new Error(s"Missing data of json type in $json"))
def setField(json: Json, key: String, value: Json) =
root.at(key).setOption(Some(value))(json)
.toRight(new Error(s"Unable to set $key -> $value to $json"))
def modifyAllPropertiesOfAllValuesWith[F[_]: Applicative](f: Json => F[Json])(json: Json): F[Json] =
root.values.each.properties.each.json.modifyF(f)(json)
val res = for {
json <- parse(jsonStr)
modifiedJson <- modifyAllPropertiesOfAllValuesWith { j =>
for {
stat <- getStat(j)
data <- getData(j)
prop <- setField(j, stat, data)
} yield prop
} (json)
} yield modifiedJson
println(res)
}
The previous answer from Gene McCulley gives a solution with Java and using class net.minidev.json. This answer is using class Gson and written in Scala.
def statDup(o: JsonObject): JsonObject = {
if (o.has("properties")) {
val a = o.get("properties").getAsJsonArray
a.foreach { e =>
val p = e.getAsJsonObject
p.add(p.get("stat").getAsString, p.get("data"))
}
} else {
o.keySet.foreach { key =>
o.get(key) match {
case jsonArr: JsonArray =>
jsonArr.foreach { e =>
statDup(e.getAsJsonObject)
}
}
}
}
o
}
Your task is to add a new field to each record under each properties in the JSON file, make the current stat value the field name and data values the new field values. The code will be rather long if you try to do it in Java.
Suggest you using SPL, an open-source Java package to get it done. Coding will be very easy and you only need one line:
A
1
=json(json(file("data.json").read()).values.run(properties=properties.(([["stat","data"]|stat]|[~.array()|data]).record())))
SPL offers JDBC driver to be invoked by Java. Just store the above SPL script as addfield.splx and invoke it in a Java application as you call a stored procedure:
…
Class.forName("com.esproc.jdbc.InternalDriver");
con= DriverManager.getConnection("jdbc:esproc:local://");
st = con.prepareCall("call addfield()");
st.execute();
…

Remove a nested JSON object with particular key, value with Jackson

I want to remove blob columns from JSON objects.
I need to check if any of the object has "#type": "blob", the entire column should be dropped.
Ex. following is a record from a DB. 'experience', 'hitpoints', 'name', 'uuid', 'image' (optional) are the columns. since the record has a blob column i.e image. It should be dropped.
Sample I/P:
{
"experience": 14248,
"hitpoints": 9223372036854775807,
"name": "Aaron1",
"uuid": "78edf902-7dd2-49a4-99b4-1c94ee286a33",
"image": {
"#type": "blob",
"content_type": "image/jpeg",
"digest": "sha1–4xlj1AKFgLdzcD7a1pVChrVTJIc=",
"length": 3888349
}
},
{
"experience": 14252,
"hitpoints": 92233720368512345,
"name": "Aaron2",
"uuid": "78edf902-7dd2-49a4-99b4-1a94ff286a45",
}
Sample O/P:
{
"experience": 14248,
"hitpoints": 9223372036854775807,
"name": "Aaron1",
"uuid": "78edf902-7dd2-49a4-99b4-1c94ee286a33",
},
{
"experience": 14252,
"hitpoints": 92233720368512345,
"name": "Aaron2",
"uuid": "78edf902-7dd2-49a4-99b4-1a94ff286a45",
}
Is there a way to achieve this by using optimized JSON parsing.
Currently, my logic follows the steps:
I'm parsing through the entire object using a function where I'm looping through the node to read the object.
Calling the 'blobChecker' function on every object.
Assigning null to the node if it contains blob.
Skipping the null node in the original function that invokes 'blobChecker'
The original function to parseJSON:
parseJsonNode(JsonNode node){
blobNodeChecker(node);
if(node!=null)
//The funtionality
}
The blobNodeChecker Function:
blobNodeChecker(JsonNode node) {
Boolean isBlob = false;
String blobNode = null;
Iterator<Map.Entry<String, JsonNode>> fields = node.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> next = fields.next();
String key = next.getKey();
String val = next.getValue().toString().toLowerCase();
if (key.equals("#type")) {
if (val.contains("blob")) {
isBlob = true;
break;
}
}
}
if (isBlob) {
node = null;
}
return node;
}
How about something like below. You can directly read a path and depending upon that delete a node. No need to loop all keys.
String tt = " {" +
" \"experience\": 14248," +
" \"hitpoints\": 9223372036854775807," +
" \"name\": \"Aaron1\"," +
" \"uuid\": \"78edf902-7dd2-49a4-99b4-1c94ee286a33\"," +
" \"image\": {" +
" \"#type\": \"blob\"," +
" \"content_type\": \"image/jpeg\"," +
" \"digest\": \"sha1–4xlj1AKFgLdzcD7a1pVChrVTJIc=\"," +
" \"length\": 3888349" +
" }" +
" }";
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
JsonFactory factory = mapper.getFactory();
JsonParser createParser = factory.createParser(tt);
JsonNode actualObj1 = mapper.readTree(createParser);
JsonNode path = actualObj1.path("image").path("#type");
if( path != null && "blob".equalsIgnoreCase(path.asText())) {
((ObjectNode)actualObj1).remove("image");
}
System.out.println(actualObj1.toString());

How to construct the JsonPath from soapui Json Response using groovy?

I have an soapui response like below and i tried to parse the same and print all the elements(From leaf node) in the json response.
Sample Json :
{
"BookID": 7982,
"author": {
"authorname": "roboin"
},
"authorid": "X-1-23",
"BookDetails": [{
"Price": "100",
"Location": "Paris"
}],
"authordob": "1980-11-10",
"Adverts": {
"online": true
}
}
Use of below groovy script is to print all the elements in the response.The below code goes to each and every element in the Json response and print like below Expected Result,
Expected Result: Print all the element(leaf node) jsonpath and values
$.['author']['authorname'] : roboin
$.['BookDetails'][0]['Price']:100
Current Result : Prints all the elements and values
authorname : roboin
Price:100
import groovy.json.*
//Get the test case response from context and parse it
def contextResponse = messageExchange.getResponseContent().toString()
//log.info(contextResponse)
def parseResponse = new JsonSlurper().parseText(contextResponse)
//log.info(parseResponse)
def parseMap(map) {
map.each {
if (it.value instanceof Map) {
parseMap(it.value)
} else if (it.value instanceof List) {
log.info(it.key + ": ")
parseArray(it.value)
} else {
log.info(it.key + ": " + it.value)
}
}
}
def parseArray(array) {
array.each {
if (it instanceof Map) {
parseMap(it)
} else if (it instanceof List) {
parseArray(it)
} else {
log.info("arrayValue: $it");
}
}
}
parseMap(parseResponse)
I tried some research about this and found few json path selector in online and that can't be used inside my soapui application.i want to iterate and print all the elements json path and their values.
Currently the above code iterate and prints only the element name and values.
def j=new groovy.json.JsonSlurper().parseText('''{
"BookID": 7982,
"author": {
"authorname": "roboin"
},
"authorid": "X-1-23",
"BookDetails": [{
"Price": "100",
"Location": "Paris"
}],
"authordob": "1980-11-10",
"Adverts": {
"online": true
}
}''')
void printJsonPaths(o, path='$'){
if(o instanceof Map){
o.each{ k,v-> printJsonPaths(v, path+"['${k}']") }
}else if(o instanceof List){
o.eachWithIndex{ v,i-> printJsonPaths(v, path+"[${i}]") }
}else{
println("${path}: ${o}")
}
}
printJsonPaths(j)
output
$['BookID']: 7982
$['author']['authorname']: roboin
$['authorid']: X-1-23
$['BookDetails'][0]['Price']: 100
$['BookDetails'][0]['Location']: Paris
$['authordob']: 1980-11-10
$['Adverts']['online']: true

Protobuf map type JSON format uses string literal "key" and "value" not the actual values

I am trying to convert a protobuf object to JSON format using com.googlecode.protobuf.format.JsonFormat but the map type came out unexpected.
My message is like this
message Response {
repeated Candidate candidates = 1;
map<string, ErrorMessage> errors = 2;
}
message ErrorMessage {
string message = 0;
ErrorType type = 1;
}
enum ErrorType {
ERROR = 0;
WARNING = 1;
}
The issue is the JSON format of the Response object I created
Response response = ...
Return new ResponseEntity<>(new JsonFormat().printToString(response), HttpStatus.OK);
I expect the errors be formatted as a map keyed by the string value (of the map key)
...
"errors": {
"someID" : {
"message": "blah blah",
"type": "ERROR"
}
}
However the actual output is (I evaluated only the new JsonFormat().printToString(response) part in intellij)
...
"errors": {
"key": "someID",
"value": {
"message": "blah blah",
"type": "ERROR"
}
}
I hope it's some small configuration I missed to make protobuf (or Jackson?) to be aware of the actual key value ? not using "key" and "value".
BTW, what's the point of having literal "key" and "value" field in a map type ? You can't do constituent lookup with it and you might just use a custom type/object.
This code works perfectly for me:
test.proto
syntax = "proto2";
package by.dev.madhead;
option java_outer_classname = "SO";
message Candidate {
}
enum ErrorType {
ERROR = 0;
WARNING = 1;
}
message ErrorMessage {
required string message = 1;
required ErrorType type = 2;
}
message Response {
repeated Candidate candidates = 1;
map<string, ErrorMessage> errors = 2;
}
App.java
public class App {
public static void main(String[] args) throws InvalidProtocolBufferException {
SO.Response response = SO.Response.newBuilder()
.addCandidates(SO.Candidate.newBuilder().build())
.addCandidates(SO.Candidate.newBuilder().build())
.addCandidates(SO.Candidate.newBuilder().build())
.putErrors("error1", SO.ErrorMessage.newBuilder().setMessage("error1").setType(SO.ErrorType.ERROR).build())
.putErrors("error2", SO.ErrorMessage.newBuilder().setMessage("error2").setType(SO.ErrorType.WARNING).build())
.build();
System.out.println(JsonFormat.printer().print(response));
}
}
The output is:
{
"candidates": [{
}, {
}, {
}],
"errors": {
"error1": {
"message": "error1",
"type": "ERROR"
},
"error2": {
"message": "error2",
"type": "WARNING"
}
}
}
Which has no keys and value as you see. Make sure that you printed not the message itself, but the result of JsonFormat.printer().print(). Basically, key and values you've seen are from internal toString() implementation of Protobuf Message.
And the full class name for JsonFormat is com.google.protobuf.util.JsonFormat, not com.googlecode.protobuf.format.JsonFormat.

Categories

Resources