Java regex to split JSON string for every object - java

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);
}
}

Related

update value by key of json array inside nested json java

I am able to update the value of jsonObject by using key name , here the method which I am using
private static JSONObject setValue(JSONObject json, String key, String newValue) throws JSONException {
Iterator<?> keys = json.keys();
while (keys.hasNext()) {
String k = (String) keys.next();
if (key.equals(k)) {
json.put(key, newValue);
}
Object value = json.opt(k);
if (value instanceof JSONObject) {
setValue((JSONObject) value, key, newValue);
}
}
return json;
}
But this is not working in case of JSONArray object , I tried surfing , tried some method but not able to get desire output , an sample request payload:
{
"sactions": [
{
"fund": "REAL",
"amount": {
"value": 130.24,
"curr": "RMB"
},
"type": "TD",
"desc": "TD",
"code": "PROMO",
"id": "deaedd69e3-6707-4b27-940a-39c3b64abdc7"
}
]
}
Looking an recursive method to update value for any given key.
This is what I tried , but did not work
public static JSONObject setProperty(JSONObject js1, String keys, String valueNew) throws JSONException {
String[] keyMain = keys.split("\\.");
for (String keym : keyMain) {
Iterator<?> iterator = js1.keys();
String key = null;
while (iterator.hasNext()) {
key = (String) iterator.next();
if ((js1.optJSONArray(key) == null) && (js1.optJSONObject(key) == null)) {
if ((key.equals(keym))) {
js1.put(key, valueNew);
return js1;
}
}
if (js1.optJSONObject(key) != null) {
if ((key.equals(keym))) {
js1 = js1.getJSONObject(key);
break;
}
}
if (js1.optJSONArray(key) != null) {
JSONArray jArray = js1.getJSONArray(key);
for (int i = 0; i < jArray.length(); i++) {
js1 = jArray.getJSONObject(i);
}
break;
}
}
}
return js1;
}
This is how I am using the method (Ceating request body using lombok and jakson)
ObjectMapper mapper = new ObjectMapper();
String.valueOf(setValue(new JSONObject(mapper.writeValueAsString(transferFund())),
field, " "))
Thanks in advance
You can utilize JsonPath jayway to save your time.
For example:
String jsonInput = "{\n" +
" \"sactions\": [\n" +
" {\n" +
" \"fund\": \"REAL\",\n" +
" \"amount\": {\n" +
" \"value\": 130.24,\n" +
" \"curr\": \"RMB\"\n" +
" },\n" +
" \"type\": \"TD\",\n" +
" \"desc\": \"TD\",\n" +
" \"code\": \"PROMO\",\n" +
" \"id\": \"deaedd69e3-6707-4b27-940a-39c3b64abdc7\"\n" +
" }\n" +
" ]\n" +
"}";
String newJson = JsonPath.parse(jsonInput).set("$..id", "test").jsonString();
System.out.println(newJson);

Compare JSONs and set values where data is null into the 1st object

colleagues! I have two objects:
1.
{
"name": "test_name",
"contact": [
{
"street": "st.1",
"phoneNumber": "test_n"
}
]
}
OR
{
"name": "test_name",
"contact": [
{
"street": "st.1",
"phoneNumber": "test_n"
}
],
"additionalInfo": null
}
2.
{
"name": "test_name_2",
"additionalInfo": {
"lastName": "ln",
"age": 24
}
}
I use JsonObject (jackson from java) and I want to set to the first object these fields that it has null, and the second does not. For example, I want to get the first object like:
{
"name": "test_name",
"contact": [
{
"street": "st.1",
"phoneNumber": "test_n"
}
],
"additionalInfo": {
"lastName": "ln",
"age": 24
}
}
Because from the first object this field is null. Is it possible to do something? Thank u!
As far as I know, there is no out of box solution for that.
Based on answer given here, I modified merge() function to meet your needs.
Here is implementation:
public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) {
Iterator<String> fieldNames = updateNode.fieldNames();
while (fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode jsonNode = mainNode.get(fieldName);
// if field exists and is an embedded object
if (jsonNode != null && jsonNode.isObject()) {
merge(jsonNode, updateNode.get(fieldName));
} else {
if (mainNode instanceof ObjectNode) {
JsonNode updateNodeValue = updateNode.get(fieldName);
JsonNode mainNodeValue = mainNode.get(fieldName);
if ((Objects.isNull(mainNodeValue) || mainNodeValue.isNull()) && (Objects.nonNull(updateNodeValue) && !updateNodeValue.isNull())) {
((ObjectNode) mainNode).put(fieldName, updateNodeValue);
}
}
}
}
return mainNode;
}
This function will basically take only those properties that exist in the object being passed as the second argument and add those properties to the object being passed as the first argument.
As far as I understand, that's what you need.
If you run code below:
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
final String jsonStringOne = "{\n" +
" \"name\": \"test_name\",\n" +
" \"contact\": [\n" +
" {\n" +
" \"street\": \"st.1\",\n" +
" \"phoneNumber\": \"test_n\"\n" +
" }\n" +
" ]\n" +
"}";
final String jsonStringTwo = "{\n" +
" \"name\": \"test_name_2\",\n" +
" \"additionalInfo\": {\n" +
" \"lastName\": \"ln\",\n" +
" \"age\": 24\n" +
" }\n" +
"}";
final JsonNode to= mapper.readTree(jsonStringOne);
final JsonNode from = mapper.readTree(jsonStringTwo);
final JsonNode jsonNode = merge(to, from);
System.out.println(jsonNode);
}
You shoud see output like this:
{
"name":"test_name",
"contact": [{"street":"st.1","phoneNumber":"test_n"}],
"additionalInfo":{"lastName":"ln","age":24}
}

How to get a single key-value from JSON using ObjectMapper in java

I have a json file called file.json which has a contents as following.
[
{
"symbol": "AAPL",
"quantity": 100,
"tradeType": "BUY",
"purchaseDate": "2019-01-02"
},
{
"symbol": "MSFT",
"quantity": 10,
"tradeType": "BUY",
"purchaseDate": "2019-01-02"
},
{
"symbol": "GOOGL",
"quantity": 50,
"tradeType": "BUY",
"purchaseDate": "2019-01-02"
}
]
I need to get the values under the field symbol as a list as ["AAPL","MSFT","GOOGL"] in JAVA using ObjectMapper. Any suggestions as to how can I get the values in the symbol? For one single json entry I have found articles as to how map the values using POJOs using getters and setters. Can I do the same for this problem? How ?
POJO
class Stock {
private String symbol;
private float quantity;
private String tradeType;
private String purchaseDate;
public String getSymbol(){
return this.symbol;
}
public float getQuantity(){
return this.quantity;
}
public String getTradeType(){
return this.tradeType;
}
public String getPurchaseDate(){
return this.purchaseDate;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public void setQuantity(float quantity) {
this.quantity = quantity;
}
public void setTradeType(String tradeType) {
this.tradeType = tradeType;
}
public void setPurchaseDate(String purchaseDate) {
this.purchaseDate = purchaseDate;
}
}
Yes you can do it.
Your JSON represents an array of objects. So, you can use the readValue method provided by Jackson and get an array of Java objects. This is where your pojo will come into play, since it will be representing the objects.
Stock[] stockArr = mapper.readValue(YourJson, Stock[].class);
You can then access elements in the array and call your getters on them.
stockArr[0].getSymbol();
To get all of the symbols simply loop through the array :)
#Test
public void testSymbols() throws IOException {
String json = "[\n" + " {\n" + " \"symbol\": \"AAPL\",\n" + " \"quantity\": 100,\n" + " \"tradeType\": \"BUY\",\n"
+ " \"purchaseDate\": \"2019-01-02\"\n" + " },\n" + " {\n" + " \"symbol\": \"MSFT\",\n"
+ " \"quantity\": 10,\n" + " \"tradeType\": \"BUY\",\n" + " \"purchaseDate\": \"2019-01-02\"\n" + " },\n"
+ " {\n" + " \"symbol\": \"GOOGL\",\n" + " \"quantity\": 50,\n" + " \"tradeType\": \"BUY\",\n"
+ " \"purchaseDate\": \"2019-01-02\"\n" + " }\n" + "]";
List list = objectMapper.readValue(json ,List.class);
List<String> symList = new ArrayList<>();
list.stream().map( m-> ((LinkedHashMap) m).get("symbol")).forEach( o->{
symList.add((String)o);
});
System.out.println(symList);
}
// output : [AAPL, MSFT, GOOGL]

How can you get the JSON Path?

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. :)

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"
} ]
}

Categories