How can you get the JSON Path? - java

Given a sample JSON:
{
"hello" : "wolrd",
"arrayField" : ["one", "two", "three"],
"mapField" : {
"name" : "john",
"lastName" : "doe"
}
}
Is there a framework in Java to help me get the JSON path structure from the JSON tree? Something similar to this:
$.hello
$.arrayField[0]
$.arrayField[1]
$.arrayField[2]
$.mapField.name
$.mapField.lastName
EDIT:
I've already coded a first approach using fasterxml's Jackson. But I'd like to know if there's something more robust / flexible.
final JsonNode rootNode = mapper.readValue(jon, JsonNode.class);
printFieldKeys(rootNode, "$");
private static void printFieldKeys(JsonNode rootNode, String parent) {
final Iterator<Entry<String, JsonNode>> fieldIt = rootNode.fields();
while (fieldIt.hasNext()) {
final Entry<String, JsonNode> next = fieldIt.next();
final JsonNode value = next.getValue();
final String path = parent + "." + next.getKey();
if (value.isValueNode()) {
System.out.println(path + " = " + value.asText());
} else {
System.out.println(path);
}
if (value.isArray()) {
for (int i = 0; i < value.size(); i++) {
printFieldKeys(value.get(i), path + "[" + i + "]");
}
} else {
printFieldKeys(value, path);
}
}
}

Take a look at this library: https://github.com/jayway/JsonPath
I believe it does exactly what you want. :)

Related

Get JSON tree based on the JSON paths list Java

My input is something like:
List.of("customer.name", "customer.phone", "shop.address", "nr")
And I have to get the JSON tree hierarchy like:
{
customer: {
name: "",
phone: "",
},
shop: {
address: ""
},
nr: ""
}
I am using Java and 'org.json', 'com.jayway.jsonpath' JSON dependencies.
Do you have any ideas, please?
Thank you!
you could do something like this:
import org.json.JSONObject;
List<String> input = List.of("customer.name", "customer.phone", "shop.address", "nr");
JSONObject root = new JSONObject();
for (String s : input) {
// Split the string by the '.' character to get the keys
String[] keys = s.split("\\.");
JSONObject obj = root;
for (int i = 0; i < keys.length; i++) {
String key = keys[i];
if (i < keys.length - 1) {
if (!obj.has(key)) {
obj.put(key, new JSONObject());
}
obj = obj.getJSONObject(key);
} else {
obj.put(key, "");
}
}
}

Java regex to split JSON string for every object

I'm trying to read data for an objects from a JSON file. I want to make my function read objects one by one, instead of as an array of objects (so i can handle exceptions for each object), so i have to make a regex, which splits each JSON object's data string. The thing is, each object contains another object in it, so i can't make the regex split at "}, {", because it will split at the inner curly braces, not the outer ones. Here is an example JSON file:
{
"vinForRepair" : "ABCDE123455432199",
"dateOfRepair" : "17/08/2021",
"mileage" : 100000,
"items" : [ {
"description" : "Water pump",
"quantity" : 1,
"price" : 120.0,
"metric" : "UNIT
}, { <---------This should be ignored
"description" : "Motor oil",
"quantity" : 1,
"price" : 30.0,
"metric" : "LITER"
} ]
}, { <---------This is where i want to split
"vinForRepair" : "ABCDE123455432100",
"dateOfRepair" : "15/08/2021",
"mileage" : 250000,
"items" : [ {
"description" : "Break fluid",
"quantity" : 1,
"price" : 20.0,
"metric" : "LITER"
}, { <---------This should be ignored
"description" : "Tyre",
"quantity" : 2,
"price" : 80.0,
"metric" : "UNIT"
} ]
}
I tried to use
String[] jsonObjectStringArray = jsonString.split("\\}, \\{\\n \"vinForRepair\"");
to split at the first object property, but it doesn't work.
I tried to use Jackson for this, but it either reads only one object, or an array of objects, and as i said i don't want to read an array directly from the file.
Instead of using regex you can do that by counting "depth" of json. When '{' occures depth is increased and otherway, when '}' occures depth is decreased. Braces at depth zero are places where you want to split.
private static List<String> split(String json) {
List<String> result = new ArrayList<>();
int depth = 0;
int start = 0;
for (int i = 0; i < json.length(); i++) {
if (json.charAt(i) == '{') {
if (depth == 0) {
start = i;
}
depth++;
}
if (json.charAt(i) == '}') {
depth--;
if (depth == 0) {
result.add(json.substring(start, i + 1));
}
}
}
return result;
}
You can run that method here https://ideone.com/vmnmCs
I'm not really in favour of using regex for marshalling/unmarshalling data in established transfer formats - that is what Jackson excels at. I would leave regex for very specific capturing scenarios, as it is notoriously difficult to maintain. I manage to cook a solution in a few minutes and should work straight out of the box with Java 11+ (provided you have the customary dependencies on apache utils, jackson, hamcrest and logback):
EDIT: My response may be already too late, but I'm standing with my decision to not implement the parsing algorithm or use regex, but rather delegate to Jackson. I've changed my example to provide more resilient handling through custom deserialization, where you can decide what to do if malformed data is encountered. Maybe it's helpful to others.
public class MyJsonDeserializer {
static final Logger logger = LoggerFactory.getLogger(MyJsonDeserializer.class);
private static String payload = "{ \n" +
" \"vinForRepair\" : \"ABCDE123455432199\",\n" +
" \"dateOfRepair\" : \"17/08/2021\",\n" +
" \"mileage\" : 100000,\n" +
" \"items\" : [ {\n" +
" \"description\" : \"Water pump\",\n" +
" \"quantity\" : 1,\n" +
" \"price\" : 120.0,\n" +
" \"metric\" : \"UNIT\"\n" +
"}, {" +
" \"description\" : \"Motor oil\",\n" +
" \"quantity\" : 1,\n" +
" \"price\" : 30.0,\n" +
" \"metric\" : \"LITER\"\n" +
" } ]\n" +
"}, {" +
" \"vinForRepair\" : \"ABCDE123455432100\",\n" +
" \"dateOfRepair\" : \"32/x8/2021\",\n" +
" \"mileage\" : 250000,\n" +
" \"items\" : [ {\n" +
" \"description\" : \"Break fluid\",\n" +
" \"quantity\" : 1,\n" +
" \"price\" : 20.0,\n" +
" \"metric\" : \"LITER\"\n" +
" }, {" +
" \"description\" : \"Tyre\",\n" +
" \"quantity\" : 2,\n" +
" \"price\" : 80.0,\n" +
" \"metric\" : \"KABOOSH\"\n" +
" } ]\n" +
"}";
static class OrderDto {
public final String vinForRepair;
public final LocalDate dateOfRepair;
public final Long mileage;
public final List<OrderItemDto> items;
#JsonCreator
public OrderDto(#JsonProperty("vinForRepair") String vinForRepair,
#JsonProperty("dateOfRepair") LocalDate dateOfRepair,
#JsonProperty("mileage") Long mileage,
#JsonProperty("items") List<OrderItemDto> items) {
this.vinForRepair = vinForRepair;
this.dateOfRepair = dateOfRepair;
this.mileage = mileage;
this.items = items;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
enum MetricEnum {
UNIT, LITER
}
static class OrderItemDto {
public final String description;
public final Integer quantity;
public final Double price;
public final MetricEnum metric;
#JsonCreator
public OrderItemDto(#JsonProperty("description") String description,
#JsonProperty("quantity") Integer quantity,
#JsonProperty("price") Double price,
#JsonProperty("metric") MetricEnum metric) {
this.description = description;
this.quantity = quantity;
this.price = price;
this.metric = metric;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
static class OrderDtoDeserializer extends StdDeserializer<OrderDto> {
public OrderDtoDeserializer(Class<?> vc) {
super(vc);
}
public OrderDtoDeserializer() {
this(null);
}
#Override
public OrderDto deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode jsonNode = jp.getCodec().readTree(jp);
String vinForRepair = jsonNode.get("vinForRepair").textValue();
LocalDate dateOfRepair = getDateOfRepair(jsonNode, ctxt);
Long mileage = jsonNode.get("mileage").longValue();
List<OrderItemDto> parsedItems = new ArrayList<>();
JsonNode items = jsonNode.get("items");
if (items.isArray()) {
ArrayNode itemsArry = (ArrayNode)items;
for (JsonNode item : itemsArry) {
String description = item.get("description").textValue();
Integer quantity = item.get("quantity").intValue();
Double price = item.get("price").doubleValue();
MetricEnum metric = getMetric(item, ctxt);
parsedItems.add(new OrderItemDto(description, quantity, price, metric));
}
}
return new OrderDto(vinForRepair, dateOfRepair, mileage, List.copyOf(parsedItems));
}
//handle any weird values
public LocalDate getDateOfRepair(JsonNode jsonNode, DeserializationContext ctxt) {
String valueToConvert = jsonNode.get("dateOfRepair").textValue();
try {
return LocalDate.parse(valueToConvert, DateTimeFormatter.ofPattern("dd/MM/yyyy"));
} catch (DateTimeParseException ex) {
JsonLocation tokenLocation = ctxt.getParser().getTokenLocation();
logger.warn("Bad input data on: [{},{}]:", tokenLocation.getLineNr(), tokenLocation.getColumnNr());
logger.warn("{}. Will use default '{}' as date value instead.", ex.getMessage(), null);
return null;
}
}
//handle any weird values
public MetricEnum getMetric(JsonNode item, DeserializationContext ctxt) {
String valueToConvert = item.get("metric").textValue();
try {
return MetricEnum.valueOf(valueToConvert);
} catch (IllegalArgumentException ex) {
JsonLocation tokenLocation = ctxt.getParser().getTokenLocation();
logger.warn("Bad input data on: [{},{}]:", tokenLocation.getLineNr(), tokenLocation.getColumnNr());
logger.warn("Unknown '{}' value - {}. Will use default '{}' instead.", valueToConvert, ex.getMessage(), MetricEnum.UNIT);
return MetricEnum.UNIT;
}
}
}
#Test
public void deserialize() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule orderDtoModule = new SimpleModule();
orderDtoModule.addDeserializer(OrderDto.class, new OrderDtoDeserializer());
mapper.registerModules(new Jdk8Module(), orderDtoModule);
CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(List.class, OrderDto.class);
//read from string
List<OrderDto> orders = mapper.readValue("[" + payload + "]", collectionType);
Assert.assertThat(orders, Matchers.notNullValue());
Assert.assertThat(orders.size(), Matchers.is(2));
logger.info("{}", orders);
}
}

How do get relativePaths in JsonNode using java and Jackson

I am working over JsonNode and trying to get relativePath and add as attribute .
public static void iterateOver(JsonNode masterJSON) {
Iterator<Entry<String, JsonNode>> nodes = masterJSON.fields();
while (nodes.hasNext()) {
Map.Entry<String, JsonNode> entry = (Map.Entry<String, JsonNode>) nodes.next();
JsonNode value = entry.getValue();
if(value.has("current") && value.has("older")) {
System.out.println("key --> " + entry.getKey() + " value-->" + value);
String relativePath = "";// ? how do get relative path e.g /obj/empdetails/data[0]/address;
((ObjectNode)entry.getValue()).put("relativePath", relativePath);
}
if(value.isArray()) {
for (int i = 0; i < value.size(); i++) {
iterateOver(value.get(i));
}
}else if (value.isObject()) {
iterateOver(value);
}
}
}
i want to have relativePath in such a way that at i can get the exact object as node.at(relativePath)

How to add data to a nested hashmap correctly

I am parsing json file and adding the schema information to a nested hash map.But when I tried to print the nested hashmap it is giving me the same values for all key.Need help on how to store data into a nested hashmap correctly.
My json file :
{
"status":"success",
"tables":[
{
"dbname":"idn",
"tableName":"my_monthly_hits_b",
"schema":"(cm11:chararray)",
"location":"/user/mydb/"
},
{
"dbname":"idn",
"tableName": "my_monthly_match",
"schema":"(city:chararray,match:chararray,cm11:chararray)",
"location":"/user/mydb1"
}
]
}
My code :
public Map<String,Map<String,String>> getDataTypes(String responsePath){
Map<String,Map<String,String>> maped = new HashMap<String,Map<String,String>>();
Map<String,String> colDataTypes = new HashMap<String,String>();
try{
JsonParser parser = new JsonParser();
Object obj = parser.parse(new FileReader(responsePath);
JsonObject jObj = (JsonObject) obj;
JsonArray jArray = (JsonArray) jObj.get("tables");
Iterator<JsonElement> itr = jArray.iterator();
while(itr.hasNext())
{
JsonObject innerObj = (JsonObject) itr.next();
JsonElement shm = innerObj.get("schema");
JsonElement jTableName = innerObj.get("tableName");
String tableName = jTableName.toString();
String ss = shm.toString().replaceAll("\"","").replaceAll("[()]",""):
System.out.println("The required JSON string --->" + ss);
if(ss.contains(","){
String[] str = ss.split(",");
for(String s: str){
String[] ptr = s.split(":");
colDataTypes.put(prt[0],ptr[1]);
}
}
else{
String[] str1 = ss.split(":");
colDataTypes.put(str1[0],str1[1]);
}
maped.put(tabName,colDataTypes);
for(String tab : maped.keySet()){
System.out.println("#####" + "Table Name " + tab + "value" + maped.get(tab));
}
}
}
catch(FileNotFoundException ex)
{
}
return maped;
}
You can use a library like Jackson to manipulate the JSON tree (shown here) or marshal the JSON to an object graph.
package foo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class Foo {
public static void main(String[] args) throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final JsonNode node = mapper.readTree("{ " +
" \"status\":\"success\"," +
" \"tables\":[ " +
" { " +
" \"dbname\":\"idn\"," +
" \"tableName\":\"my_monthly_hits_b\"," +
" \"schema\":\"(cm11:chararray)\"," +
" \"location\":\"/user/mydb/\"" +
" }," +
" { " +
" \"dbname\":\"idn\"," +
" \"tableName\":\"my_monthly_match\"," +
" \"schema\":\"(city:chararray,match:chararray,cm11:chararray)\"," +
" \"location\":\"/user/mydb1\"" +
" }" +
" ]" +
"}");
final ArrayNode tables = (ArrayNode) node.get("tables");
// Add a new schema
final ObjectNode newSchema = tables.addObject();
newSchema.put("dbname", "foo db name");
newSchema.put("tableName", "foo table name");
newSchema.put("schema", "(foo:chararray,bar:chararray)");
newSchema.put("location", "/foo/bar");
mapper.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(mapper.writeValueAsString(node));
}
}
The example above will print:
{
"status" : "success",
"tables" : [ {
"dbname" : "idn",
"tableName" : "my_monthly_hits_b",
"schema" : "(cm11:chararray)",
"location" : "/user/mydb/"
}, {
"dbname" : "idn",
"tableName" : "my_monthly_match",
"schema" : "(city:chararray,match:chararray,cm11:chararray)",
"location" : "/user/mydb1"
}, {
"dbname" : "foo db name",
"tableName" : "foo table name",
"schema" : "(foo:chararray,bar:chararray)",
"location" : "/foo/bar"
} ]
}

Library to convert JSON to urlencoded

We are doing some integration towards a quite inconsistent (Zurmo-)REST API. The API only accepts urlencoded strings as its payload in the http posts, but it answers with JSON.
So as the documentation was very unclear on this we naturally thought we could post JSON to it, but this was not the case.
So now we have all our code generating JSON when we need to send it as x-www-form-urlencoded, is there any java library that can do a conversion from JSON to an urlencoded string?
We are currently using the org.json lib, but we can change it if there would be a need for it.
Example:
This JSON string:
{"data":{"description":"test","occurredOnDateTime":"2013-10-24 01:44:50"}}
Should be converted into this:
data%5Bdescription%5D=test&data%5BoccurredOnDateTime%5D=2013-10-24+01%3A44%3A50
Java code:
We translated rasmushaglunds javascript code to java and wrapped it, here is the result if anybody else stumbles upon this problem.
public static String jsonToURLEncoding(JSONObject json) {
String output = "";
String[] keys = JSONObject.getNames(json);
for (String currKey : keys)
output += jsonToURLEncodingAux(json.get(currKey), currKey);
return output.substring(0, output.length()-1);
}
private static String jsonToURLEncodingAux(Object json, String prefix) {
String output = "";
if (json instanceof JSONObject) {
JSONObject obj = (JSONObject)json;
String[] keys = JSONObject.getNames(obj);
for (String currKey : keys) {
String subPrefix = prefix + "[" + currKey + "]";
output += jsonToURLEncodingAux(obj.get(currKey), subPrefix);
}
} else if (json instanceof JSONArray) {
JSONArray jsonArr = (JSONArray) json;
int arrLen = jsonArr.length();
for (int i = 0; i < arrLen; i++) {
String subPrefix = prefix + "[" + i + "]";
Object child = jsonArr.get(i);
output += jsonToURLEncodingAux(child, subPrefix);
}
} else {
output = prefix + "=" + json.toString() + "&";
}
return output;
}
public static String objectToUrlEncodedString(Object object, Gson gson) {
return jsonToUrlEncodedString((JsonObject) new JsonParser().parse(gson.toJson(object)));
}
private static String jsonToUrlEncodedString(JsonObject jsonObject) {
return jsonToUrlEncodedString(jsonObject, "");
}
private static String jsonToUrlEncodedString(JsonObject jsonObject, String prefix) {
String urlString = "";
for (Map.Entry<String, JsonElement> item : jsonObject.entrySet()) {
if (item.getValue() != null && item.getValue().isJsonObject()) {
urlString += jsonToUrlEncodedString(
item.getValue().getAsJsonObject(),
prefix.isEmpty() ? item.getKey() : prefix + "[" + item.getKey() + "]"
);
} else {
urlString += prefix.isEmpty() ?
item.getKey() + "=" + item.getValue().getAsString() + "&" :
prefix + "[" + item.getKey() + "]=" + item.getValue().getAsString() + "&";
}
}
return urlString;
}
There is an easier way now, and that is to use the URLEncoder.encode method.
Import the URLEncoder package:
import java.net.URLEncoder;
and then:
URLEncoder.encode(objectMapper.writeValueAsString(<yourClass>), StandardCharsets.UTF_8.toString());
You can test your result here:
https://onlinejsontools.com/url-decode-json
As noted below, it's not a Java library but you should be able to translate it :)
Here's how you could do it in javascript:
var jsonArrayToUrl = function (obj, prefix) {
var urlString = "";
for (var key in obj) {
if (obj[key] !== null && typeof obj[key] == "object") {
prefix += "[" + key + "]";
urlString += jsonArrayToUrl(obj[key], prefix);
}else{
urlString += prefix + "[" + key + "]=" + obj[key] + "&";
}
}
return encodeURIComponent(urlString);
};
Then call it with
jsonArrayToUrl(test["data"], "data");
By the example string you gave above it returns
"data%5Bdescription%5D%3Dtest%26data%5BoccurredOnDateTime%5D%3D2013-10-24%2001%3A44%3A50%26"
It should work recursively on nested arrays. You might also consider writing a wrapper for the function so that you only need one argument.

Categories