I'd like to use Java to take in a JSON body, iterate through the records, and output every other record into a new JSON array. Each set of two records will be its own array, and I only need to take the first one in each respective array. I'll also be providing the column names in the original request that need to be added to the subsequent flattened object. For example, let's say I have the following JSON request body:
{
"records": [
[
[
"0DFC29E2-700E-4CC1-931E-B61DF4954B6B",
"John Doe",
"Teacher",
"China"
],
[
"B5B9186E-CE65-4911-8516-C510D3CC3ACE",
"Jane Doe",
"Doctor",
"London"
]
],
[
[
"20C4DD07-4E96-47F8-A1E1-B20B4C48120C",
"Jim Doe",
"Lawyer",
"Canada"
],
[
"76718CB1-238F-418E-BD14-5E2867FF3FB4",
"Jack Doe",
"Chef",
"Mexico"
]
]
],
"columns": [
"ID",
"Name",
"Occupation",
"Location"
]
}
I'd then like this request body flattened to the following:
[{
"ID": "0DFC29E2-700E-4CC1-931E-B61DF4954B6B",
"Name": "John Doe",
"Occupation": "Teacher",
"Location": "China"
},
{
"ID": "20C4DD07-4E96-47F8-A1E1-B20B4C48120C",
"Name": "Jim Doe",
"Occupation": "Lawyer",
"Location": "Canada"
}]
I'd like this code to be pretty dynamic, so it doesn't explicitly reference the column names in code. That way I can pass up other column names in the future if I have a different JSON body structure, and it will work accordingly. I'll always be passing up the data with a title of "records" so that's okay to hardcode. Any help is greatly appreciated.
You should convert source JSON into collection of maps. Each map will be contain property names and property values. After that, you can easily serialize it to expected format. In below example I use Jackson library, but I think you should be able to use Gson library too.
At first, we should define SourceEntity class which define all properties for input JSON.
class SourceEntity {
private String[][][] records;
private String[] columns;
public String[][][] getRecords() {
return records;
}
public void setRecords(String[][][] records) {
this.records = records;
}
public String[] getColumns() {
return columns;
}
public void setColumns(String[] columns) {
this.columns = columns;
}
}
After that, we should write converter, which can parse input JSON, convert arrays into collection of maps and serialize it to target JSON.
class JsonConverter {
private ObjectMapper objectMapper = new ObjectMapper();
private JsonFactory jsonFactory = new JsonFactory();
public String convert(File sourceJsonFile) throws Exception {
SourceEntity sourceEntity = parseSourceEntity(sourceJsonFile);
List<Map<String, String>> result = convertToTargetPropertiesMap(sourceEntity);
return objectMapper.writeValueAsString(result);
}
private SourceEntity parseSourceEntity(File sourceJsonFile)
throws Exception {
JsonParser parser = jsonFactory.createJsonParser(sourceJsonFile);
return objectMapper.readValue(parser, SourceEntity.class);
}
private List<Map<String, String>> convertToTargetPropertiesMap(
SourceEntity entity) {
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
for (String[][] pairs : entity.getRecords()) {
list.add(createPropertyMap(entity.getColumns(), pairs[0]));
}
return list;
}
private Map<String, String> createPropertyMap(String[] names,
String[] values) {
Map<String, String> propertyMap = new LinkedHashMap<String, String>();
for (int i = 0; i < values.length; i++) {
propertyMap.put(names[i], values[i]);
}
return propertyMap;
}
}
Finally, we should write a little test:
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
JsonConverter converter = new JsonConverter();
String result = converter.convert(new File("/tmp/source.json"));
System.out.println(result);
}
}
Above program prints this JSON for your example input:
[{"ID":"0DFC29E2-700E-4CC1-931E-B61DF4954B6B","Name":"John Doe","Occupation":"Teacher","Location":"China"},{"ID":"20C4DD07-4E96-47F8-A1E1-B20B4C48120C","Name":"Jim Doe","Occupation":"Lawyer","Location":"Canada"}]
You can read the source JSON into a bunch of Java objects, do the transform on the Java side, and output in the new format.
It would be nice if there were a JSON equivalent to XSLT, but I haven't seen one that's in general use.
Related
i'm trying to add new data to existing json file that named question.json but it's not working! it create a new file, can someone help me please!
mycode: i'm using json-simple1.1
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
public class Main {
public static void writeToJson() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("question", "q3");
ArrayList<String>anss = new ArrayList<>();
anss.add("a1");
anss.add("a2");
anss.add("a3");
anss.add("a4");
JSONArray arr = new JSONArray();
arr.add(anss.get(0));
arr.add(anss.get(1));
arr.add(anss.get(2));
arr.add(anss.get(3));
jsonObject.put("answers",arr);
jsonObject.put("correct_ans", "2");
jsonObject.put("level", "2");
jsonObject.put("team", "animal");
try {
FileWriter file = new FileWriter("json/quetion.json");
file.write(jsonObject.toJSONString());
file.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[]args) {
writeToJson();
}
}
{
"questions":[
{
"question": "q1",
"answers": [
"answer1",
"answer2",
"answer3",
"answer4"
],
"correct_ans": "2",
"level": "1",
"team": "animal"
},
{
"question": "q2",
"answers": [
"answer1",
"answer2",
"answer3",
"answer4"
],
"correct_ans": "1",
"level": "2",
"team": "animal"
}
]
}
this is the json file i want to add what i wrote in the code to this json file but i failed! i need someone to tell me how can i add a new json object like {"question" : "q2" ...} without changing the format of the json file or creating a new json file.
org.json.simple
The structure of your JSON has more levels of nesting than can be observed in your code therefore your result doesn't match.
That's what you have in the JSON:
JSONObject { field : JSONArray [ JSONObject { field : value, field : JSONArray, ... }
I.e. JSONObject which contains a JSONArray which might contain several JSONObjects which in turn contain a JSONArray.
That's how it can be translated into the code (to avoid redundancy logic which for creating a nested JSONObject was extracted into a separate method):
JSONObject root = new JSONObject();
JSONArray questions = new JSONArray();
JSONObject question1 = createQuestion(
"q1", "2", "1", "animal",
"answer1", "answer2", "answer3", "answer4"
);
JSONObject question2 = createQuestion(
"q2", "1", "2", "animal",
"answer1", "answer2", "answer3", "answer4"
);
Collections.addAll(questions, question1, question2);
root.put("questions", questions);
public static JSONObject createQuestion(String questionId,
String correctAnswer,
String level, String team,
String... answers) {
JSONObject question = new JSONObject();
question.put("question", questionId);
JSONArray answersArray = new JSONArray();
Collections.addAll(answersArray, answers);
question.put("answers", answersArray);
question.put("correct_ans", correctAnswer);
question.put("level", level);
question.put("team", team);
return question;
}
That's it.
There's a lot of low-level logic which you can eliminate if you would choose a more mature tool for parsing JSON like Jackson, Gson.
Jackson
Here's an example using Jackson library.
Firstly, let's create two POJO: one representing a single question and another wrapping a list of question. For convince, and also in order to avoid posting boilerplate code, I would use Lombock's.
Question class:
#Builder
#AllArgsConstructor
#Getter
public static class Question {
private String questionId;
private List<String> answers;
private String correctAnswer;
private String level;
private String team;
}
Questions class:
#AllArgsConstructor
#Getter
public static class Questions {
private List<Question> questions;
}
Here's how such objects can be serialized:
Question question3 = Question.builder()
.questionId("q1")
.answers(List.of("answer1", "answer2", "answer3", "answer4"))
.correctAnswer("2")
.level("1")
.team("animal")
.build();
Question question4 = Question.builder()
.questionId("q2")
.answers(List.of("answer1", "answer2", "answer3", "answer4"))
.correctAnswer("1")
.level("2")
.team("animal")
.build();
Questions questions1 = new Questions(List.of(question3, question4));
ObjectMapper mapper = new ObjectMapper();
String json1 = mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(questions1);
System.out.println(json1);
Output:
{
"questions" : [ {
"questionId" : "q1",
"answers" : [ "answer1", "answer2", "answer3", "answer4" ],
"correctAnswer" : "2",
"level" : "1",
"team" : "animal"
}, {
"questionId" : "q2",
"answers" : [ "answer1", "answer2", "answer3", "answer4" ],
"correctAnswer" : "1",
"level" : "2",
"team" : "animal"
} ]
}
I have a JSON as follows
[
{
"a": "John",
"id": "6",
"c": "val1"
},
{
"a": "Jack",
"id": "6",
"c": "val2"
},
{
"a": "Joe",
"id": "6",
"c": "val3"
}
]
I need to convert it into a Map<String, String> such that the values of the fields 'a' become the key and the values of the fields 'c' become the value in the Map.
In other words, my Map should look like the below:
John:val1
Jack:val2
Joe:val3
What is the shortest way to do this?
Also, I was wondering if in any way RestAssured GPath can be leveraged here
Something like this -
new JsonPath(jsonPayload).getString("findAll { json -> json.id == '6' }.a");
Are u looking for JsonSlurper ?
import groovy.json.JsonSlurper
String json = '''
[
{
"a": "John",
"id": "6",
"c": "val1"
},
{
"a": "Jack",
"id": "6",
"c": "val2"
},
{
"a": "Joe",
"id": "6",
"c": "val3"
}
]
'''
def root = new JsonSlurper().parseText(json)
def result = root.findAll{it.id == '6'}.collectEntries{[it.a, it.c]}
print(result)
You can use jackson for example:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2.2</version>
</dependency>
I would create a wrapper for the resulting Map and a custom deserializer.
#JsonDeserialize(using = MapWrapperDeserializer.class)
public class MapWrapper {
private final Map<String, String> map;
public MapWrapper(Map<String, String> map) {
this.map = map;
}
public Map<String, String> getMap() {
return this.map;
}
}
The deserializer:
public class MapWrapperDeserializer extends StdDeserializer<MapWrapper> {
public MapWrapperDeserializer() {
super(MapWrapper.class);
}
#Override
public MapWrapper deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode array = parser.getCodec().readTree(parser);
int size = array.size();
Map<String, String> map = new LinkedHashMap<>(size);
for (JsonNode element : array) {
String key = element.get("a").asText();
String value = element.get("c").asText();
map.put(key, value);
}
return new MapWrapper(map);
}
}
A simple test:
public class Temp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
InputStream dataStream = getInputStreamOrJsonString();
MapWrapper wrapper = mapper.readValue(dataStream, MapWrapper.class);
System.out.println(wrapper.getMap());
}
}
You can use jsonpath library to make it. Add it into your pom.xml:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
Then try following code
// read the a, c of json string by the JsonPath libraby
List<String> aList = JsonPath.read(json, "$.[*].a");
List<String> cList = JsonPath.read(json, "$.[*].c");
// combine two list to a map
Iterator<String> i1 = aList.iterator();
Iterator<String> i2 = cList.iterator();
Map<String, String> map = new HashMap<>();
while (i1.hasNext() && i2.hasNext()) {
map.put(i1.next(), i2.next());
}
// print it
map.forEach((k,v) -> System.out.println(k + ":" + v));
See more about jsonpath
You can convert it first to a list of YourObject and them convert it to a map following the rules you want ( key = a, value = c)
Create a class that represent the json object:
class YourObject
{
String a;
String id;
String c;
// contructors
// getters and setters
}
Desserialize your JSON into it using Gson:
String json = "<PUT YOUR JSON HERE>";
List<YourObject> list = new GsonBuilder().create().fromJson(json, new TypeToken<List<YourObject>>(){}.getType());
Then transform it to a map:
Map<String, String> map = list.stream().collect(Collectors.toMap(YourObject::getA, YourObject::getC));
If you want to filter by an specific id (forxample id=6) you can do like that:
Map<String, String> map = list.stream()
.filter(yo -> yo.getId().equals("6"))
.collect(Collectors.toMap(YourObject::getA, YourObject::getC));
Say I have to compose below JSON string response to a HTTP get request:
{
"filename": "100055_1_0920_082714_014",
"sfyc": "1",
"rect": [
{"type1": ["1145", "1027", "1954", "1259"]},
{"type2": ["1527", "788", "569", "418"]},
{"type1": ["4053", "773", "915", "449"]}
]
}
I think if I want to compose the JSON string, I need a JSON Object({"type1": ["1145", "1027", "1954", "1259"]}),wihch can be transformed from a Java Class which has a feild type1 of type List<String>, and another Class for type2. Then Gson or Jackson can help in my case.
Problem is that I have more than 20 different types, so I don't think defining all of these Java classes is a good choice. So how can I have the wanted JSON String in a smart way?
Note:
Each type may appear more than once(like type1 in my code) so map should not be the answer.
You may use a dto like
public class Dto {
private String fileName;
private Integer sfyc;
private List<Map<String, List<String>>> rect;
}
Try running the main method to verify
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
public class Test {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Dto dto = getDummy();
System.out.println(mapper.writeValueAsString(dto));
}
#Data
public static class Dto {
private String fileName;
private Integer sfyc;
private List<Map<String, List<String>>> rect;
}
public static Dto getDummy() {
Dto dto = new Dto();
dto.setFileName("100055_1_0920_082714_014");
dto.setSfyc(1);
List<String> type1List = List.of("1145", "1027", "1954", "1259");// of function for java 9 or newer
List<String> type2List = List.of("1527", "788", "569", "418");
List<String> type3List = List.of("4053", "773", "915", "449");
List<Map<String, List<String>>> maps =
List.of(
Map.of("type1", type1List),
Map.of("type2", type2List),
Map.of("type1", type3List));
dto.setRect(maps);
return dto;
}
}
it will produce an out put like
{
"fileName": "100055_1_0920_082714_014",
"sfyc": 1,
"rect": [
{
"type1": [
"1145",
"1027",
"1954",
"1259"
]
},
{
"type2": [
"1527",
"788",
"569",
"418"
]
},
{
"type1": [
"4053",
"773",
"915",
"449"
]
}
]
}
I think the solution for this, is to use a map to store the custom types, So that we don't have to create a new class for each type. I was able to achieve this using following code.
POJO class.
public class Example {
#JsonProperty("filename")
private String filename;
#JsonProperty("sfyc")
private String sfyc;
#JsonProperty("rect")
private List<Map<String,List<String>>> rect;
#JsonProperty("filename")
public String getFilename() {
return filename;
}
#JsonProperty("filename")
public void setFilename(String filename) {
this.filename = filename;
}
#JsonProperty("sfyc")
public String getSfyc() {
return sfyc;
}
#JsonProperty("sfyc")
public void setSfyc(String sfyc) {
this.sfyc = sfyc;
}
/**
* #return the list
*/
#JsonProperty("rect")
public List<Map<String, List<String>>> getList() {
return rect;
}
/**
* #param list the list to set
*/
#JsonProperty("list")
public void setList(List<Map<String, List<String>>> rect) {
this.rect = rect;
}
}
Main class to test this change.
public class TestJsonProblem {
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
Example example = new Example();
example.setFilename("Test");
example.setSfyc("1");
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("type1", Arrays.asList("1145", "1027", "1954", "1259"));
map.put("type2", Arrays.asList("1145", "1027", "1954", "1259"));
map.put("type3", Arrays.asList("1145", "1027", "1954", "1259"));
List<Map<String, List<String>>> list = new ArrayList();
list.add(map);
example.setRect(list);
ObjectMapper mapper = new ObjectMapper();
String jsonData = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(example);
System.out.println(jsonData);
}
}
Output :
{
"filename" : "Test",
"sfyc" : "1",
"rect" : [ {
"type3" : [ "1145", "1027", "1954", "1259" ],
"type2" : [ "1145", "1027", "1954", "1259" ],
"type1" : [ "1145", "1027", "1954", "1259" ]
} ]
}
{
"filename": "100055_1_0920_082714_014",
"sfyc": "1",
"rect": [
{
"type1": [
"1145",
"1027",
"1954",
"1259"
]
},
{
"type2": [
"1527",
"788",
"569",
"418"
]
},
{
"type3": [
"4053",
"773",
"915",
"449"
]
}
]
}
and use this as Web site
http://www.jsonschema2pojo.org/
Requirements:
I want to apply some functions on the inner values of the JsonNode. The functions can be different eg:- lowercasing some values or appending something to the values or replace the values with something. How can I achieve that using Jackson library? Note that the structure of the JSON data can be different which means I want to build a generic system which will accept some path expression which will basically decide where to change. I want to use functional programming style, so that I can pass these functions as arguments.
eg:
input:
{
"name": "xyz",
"values": [
{
"id": "xyz1",
"sal": "1234",
"addresses": [
{
"id": "add1",
"name": "ABCD",
"dist": "123"
},
{
"id": "add2",
"name": "abcd3",
"dist": "345"
}
]
},
{
"id": "xyz2",
"sal": "3456",
"addresses": [
{
"id": "add1",
"name": "abcd",
"dist": "123"
},
{
"id": "add2",
"name": "XXXXX",
"dist": "345"
}
]
}
]
}
In this case I have to two functions basically, lowercase() and convert_to_number(). I want to apply lowercase() function on all the "name" attribute inside all the "addresses" of each "value".
same goes for convert_to_number() , but for all the "dist" attribute.
So, basically the JSON expressions will be something like below for the functions:
lowercase() : /values/*/addresses/*/name
convert_to_number() : /values/*/addresses/*/dist
output:
{
"name": "xyz",
"values": [
{
"id": "xyz1",
"sal": "1234",
"addresses": [
{
"id": "add1",
"name": "abcd",
"dist": 123
},
{
"id": "add2",
"name": "abcd3",
"dist": 345
}
]
},
{
"id": "xyz2",
"sal": "3456",
"addresses": [
{
"id": "add1",
"name": "abcd",
"dist": 123
},
{
"id": "add2",
"name": "xxxx",
"dist": 345
}
]
}
]
}
Client code:
JsonNode jsonNode = ...
applyFunctionsRecursivelyBasedOnExpr(JsonNode jsonNode, String expr, Function )
As #MichalZiober in his answer already pointed out,
JsonPath offers a much more powerful API than Jackson,
when you need to do JSON-path-based operations.
Using methods JsonPath.parse and WriteContext.map
you can solve your problem in just a few lines:
import java.io.File;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("input.json");
String json = JsonPath.parse(file)
.map("$.values[*].addresses[*].name", Main::lowerCase)
.map("$.values[*].addresses[*].dist", Main::convertToNumber)
.jsonString();
System.out.println(json);
}
private static Object lowerCase(Object currentValue, Configuration configuration) {
if (currentValue instanceof String)
return ((String)currentValue).toLowerCase();
return currentValue;
}
private static Object convertToNumber(Object currentValue, Configuration configuration) {
if (currentValue instanceof String)
return Integer.valueOf((String)currentValue);
return currentValue;
}
}
JsonPath
You could use JsonPath library which has a better JSON Path handling. When Jackson supports only JSON Pointer draft-ietf-appsawg-json-pointer-03. Take a look on JsonPointer documentation. With JsonPath library you could do that in this way:
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import net.minidev.json.JSONArray;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
JsonModifier jsonModifier = new JsonModifier(jsonFile);
Function<Map<String, Object>, Void> lowerCaseName = map -> {
final String key = "name";
map.put(key, map.get(key).toString().toLowerCase());
return null;
};
Function<Map<String, Object>, Void> changeDistToNumber = map -> {
final String key = "dist";
map.put(key, Integer.parseInt(map.get(key).toString()));
return null;
};
jsonModifier.update("$.values[*].addresses[*]", Arrays.asList(lowerCaseName, changeDistToNumber));
jsonModifier.print();
}
}
class JsonModifier {
private final DocumentContext document;
public JsonModifier(File json) throws IOException {
this.document = JsonPath.parse(json);
}
public void update(String path, List<Function<Map<String, Object>, Void>> transformers) {
JSONArray array = document.read(path);
for (int i = 0; i < array.size(); i++) {
Object o = array.get(i);
transformers.forEach(t -> {
t.apply((Map<String, Object>) o);
});
}
}
public void print() {
System.out.println(document.jsonString());
}
}
Your path, should work on JSON object-s which are represented by Map<String, Object>. You can replace keys in given object, add them, remove them just like replacing, adding and removing keys in Map.
Jackson
You can of course mock JsonPath feature by iterating over Json Pointer. For each * we need to create loop and iterate over it using counter and until node is not missing. Below you can see simple implementation:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
JsonNode root = mapper.readTree(jsonFile);
Function<ObjectNode, Void> lowerCaseName = node -> {
final String key = "name";
node.put(key, node.get(key).asText().toLowerCase());
return null;
};
Function<ObjectNode, Void> changeDistToNumber = node -> {
final String key = "dist";
node.put(key, Integer.parseInt(node.get(key).asText()));
return null;
};
JsonModifier jsonModifier = new JsonModifier(root);
jsonModifier.updateAddresses(Arrays.asList(lowerCaseName, changeDistToNumber));
System.out.println(mapper.writeValueAsString(root));
}
}
class JsonModifier {
private final JsonNode root;
public JsonModifier(JsonNode root) {
this.root = root;
}
public void updateAddresses(List<Function<ObjectNode, Void>> transformers) {
String path = "/values/%d/addresses/%d";
for (int v = 0; v < 100; v++) {
int a = 0;
do {
JsonNode address = root.at(String.format(path, v, a++));
if (address.isMissingNode()) {
break;
}
if (address.isObject()) {
transformers.forEach(t -> t.apply((ObjectNode) address));
}
} while (true);
if (a == 0) {
break;
}
}
}
}
This solution is slower than with JsonPath because we need to travers whole JSON tree n times where n number of matching nodes. Of course, our implementation could be a much faster using Streaming API.
I have a JSON with list of Objects and any of the item in the list can have null or the same object as a value for a key. I am looking for a faster way to parse the json to arrive at my final result.
The data structure looks like -
[
{
"firstName": "Bruce",
"familyMembers": null
},
{
"firstName": "Gates Family",
"familyMembers": [
{
"firstName": "Bill",
"familyMembers": null
},
{
"firstName": "Steve",
"familyMembers": null
}
]
},
{
"firstName": "Lee",
"familyMembers": null
},
{
"firstName": "Chan",
"familyMembers": null
}
]
The output should be a set = ("Bruce", "Bill", "Steve", "Lee", "Chan").
I am looking for a best possible way to do this in Java, such that i dont increase my response time by getting caught in this parsing hell.
Appreciate your time on this.
Try my recursive implementation.
public static void jsonArrayToSet(JSONArray jAry, Set<String> result, String targetKey, String subArrayKey, boolean includeNode){
try {
for (int i = 0; i < jAry.length(); i++) {
JSONObject jObj = jAry.getJSONObject(i);
boolean hasSubArray = false;
JSONArray subArray = null;
if(jObj.has(subArrayKey)){
Object possibleSubArray = jObj.get(subArrayKey);
if(possibleSubArray instanceof JSONArray){
hasSubArray = true;
subArray = (JSONArray) possibleSubArray;
}
}
if(hasSubArray){
if(includeNode){
result.add(jObj.getString(targetKey));
}
jsonArrayToSet(subArray, result, targetKey, subArrayKey, includeNode);
} else {
result.add(jObj.getString(targetKey));
}
}
} catch (JSONException e){
e.printStackTrace();
}
}
jAry: The source JSONArray.
result: The Set you want to write in.
targetKey: The key that maps to an entry which you want to add to result.
subArrayKey: The key that map to a sub-JSONArray.
includeNode: When current JSONOnject is a node containing sub-array, add it to result or not.
In your case, you can call:
jsonArrayToSet(yourJsonArray, yourSet, "firstName", "familyMembers", false);
As mentioned in my comment.
Your first issue would be the content in your JSON file. Based on the standard, it should be wrapped around with a set of { }.
Example
{
"members": [
{
"firstName": "Bruce",
"familyMembers": null
},
{
"firstName": "Gates Family",
"familyMembers": [
{
"firstName": "Bill",
"familyMembers": null
},
{
"firstName": "Steve",
"familyMembers": null
}
]
},
{
"firstName": "Lee",
"familyMembers": null
},
{
"firstName": "Chan",
"familyMembers": null
}
]
}
Also, I think the value "Gates Family" should be part of the output? Since it is under the "FirstName" attribute.
Anyway, here is my solution that is based on the org.json library. It also uses Goggle's GSon library where I use it for reading the JSON file.
import org.json.*;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
public class solution {
public static final String JSON_DATA_FILE_PATH = "./data/source_37848106.json";
private static boolean hasMoreFamilyName(JSONObject json) {
return json.has("familyMembers") && json.get("familyMembers") != JSONObject.NULL;
}
private static void trackFirstName(Map<String, Integer> nameTracker, JSONObject json) {
if (!nameTracker.containsKey(json.getString("firstName"))) {
nameTracker.put(json.getString("firstName"), /*DUMMY VALUE =*/1);
}
}
private static void getNames(Map<String,Integer> nameTracker, JSONArray jsonArr) {
for (int i = 0; i< jsonArr.length(); i++) {
JSONObject item = jsonArr.getJSONObject(i);
if (hasMoreFamilyName(item)) {
getNames(nameTracker, item.getJSONArray("familyMembers"));
}
trackFirstName(nameTracker, item);
}
}
public static void main(String[] args) {
Map<String, Integer> nameTracker = new HashMap<>();
try {
String text = Files.toString(new File(JSON_DATA_FILE_PATH), Charsets.UTF_8);
JSONObject json = new JSONObject(text);
getNames(nameTracker, json.getJSONArray("members"));
}
catch (Exception ex) {
System.out.println("Something is wrong.");
}
for (Map.Entry<String,Integer> entry : nameTracker.entrySet()) {
System.out.println(entry.getKey());
}
}
You can use ObjectMapper defined in jackson-databind-2.6.5.jar
Define a java class with fields similar to json pattern and then just bind them like:
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
Family family=objectMapper.readValue(jsonString, Family.class);
Now you will have a java object similar to your json string pattern and you can print it the way you like.