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.
Related
I need to compare two Json: old and new. But I only need to update the old json with new content from the new json. For example:
oldJson.json:
[
{
"thumbnail": "1"
},
{
"thumbnail": "2"
}
]
newJson.json:
[
{
"thumbnail": "1"
},
{
"thumbnail": "2"
},
{
"thumbnail": "3"
}
]
Desired outcome for oldJson.json:
[
{
"thumbnail": "1"
},
{
"thumbnail": "2"
},
{
"thumbnail": "3"
}
]
How should I approach this?
Since the Jsons are JsonArray, hence the need of Json.createDiff(source.asJsonArray(), target.asJsonArray());, instead of Json.createDiff(source.asJsonObject(), target.asJsonObject());
Please see comments in code for further details.
Also for reference please visit Comparing JSON documents in Java with JSON-P
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonPatch;
import javax.json.JsonValue;
public class App {
public static void main(String[] args) throws IOException {
Path oldJsonPath = Paths.get("./oldJson.json");
Path newJsonPath = Paths.get("./newJson.json");
String leftJsonDoc = Files.readString(oldJsonPath);
String rightJsonDoc = Files.readString(newJsonPath);
JsonValue source = Json.createReader(new StringReader(leftJsonDoc)).readValue();
JsonValue target = Json.createReader(new StringReader(rightJsonDoc)).readValue();
JsonPatch diff = Json.createDiff(source.asJsonArray(), target.asJsonArray());
// Create a container to collect only operation with add
JsonArrayBuilder container = Json.createArrayBuilder();
// loop through JsonArray to look for JsonObject with "op: add"
for (JsonValue jsonObj : diff.toJsonArray()) {
// get JSONObject(s) with "op: add"
if (jsonObj.toString().contains("op\":\"add")) {
container.add(jsonObj.asJsonObject()); // add it to container
}
}
// Build the JsonArrayBuilder with the payload
JsonPatch payload = Json.createPatch(container.build());
JsonValue patchedPayload = payload.apply(source.asJsonArray());
}
}
Want to Process following JSON string (Validated with jsonlint.com)
[{
"label": "Hospital",
"domain": "Health_Care",
"synonymlabels": [{
"label": "SHCO"
}, {
"label": "HCO"
}],
"childrenlabels": [{
"label": "Childern_Hospital"
}, {
"label": "Mental_Hospital"
}, {
"label": "Heart_Hospital"
}, {
"label": "Orthopadic_Hospital"
}, {
"label": "General_Hospital"
}, {
"label": "Gynac_Hospital"
}, {
"label": "Cancer_Hospital"
}, {
"label": "Burn_Hospital"
}, {
"label": "Trauma_Care_Hospital"
}]
},
{
"label": "Doctor",
"domain": "Health_Care",
"synonymlabels": [{
"label": "Clinician"
}, {
"label": "Physician"
}, {
"label": "Medical_Practitioner"
}],
"childrenlabels": [{
"label": "Cardiaologist"
}, {
"label": "Allergist"
}, {
"label": "Nurologist"
}, {
"label": "Gynacologist"
}, {
"label": "General_Physician"
}, {
"label": "Anesthetist"
}, {
"label": "Physiotherapist"
}, {
"label": "Urologist"
}, {
"label": "Oncologist"
}, {
"label": "Homeopath"
}, {
"label": "Dentist"
}]
}
]
Sample Code
I am able to run the following sample code and able to get the desired output. If I change JSON string i.e. object "{}" to JSON ARRAY "[{},{},{}]" to parse and necessary change in the code (no idea that how to deal with the Array) then I'm getting no results in the console. Feeling paralytic in finding my error. Please help. Struggled for almost a day in tweaking the code.
import java.io.IOException;
import java.io.StringReader;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
public class gsontester {
public static void main(String args[]) {
String jsonString =
"{ \"name\":\"Mahesh Kumar\", \"age\":21,\"verified\":false,\"marks\": [100,90,85,100,14,95]}";
JsonReader reader = new JsonReader(new StringReader(jsonString));
try {
handleJsonObject(reader);
}
catch (IOException e) {
e.printStackTrace();
}
}
private static void handleJsonObject(JsonReader reader) throws IOException {
reader.beginObject();
String fieldname = null;
while (reader.hasNext()) {
JsonToken token = reader.peek();
if (token.equals(JsonToken.BEGIN_ARRAY)) {
System.out.print("Marks [ ");
handleJsonArray(reader);
System.out.print("]");
} else if (token.equals(JsonToken.END_OBJECT)) {
reader.endObject();
return;
} else {
if (token.equals(JsonToken.NAME)) {
//get the current token
fieldname = reader.nextName();
}
if ("name".equals(fieldname)) {
//move to next token
token = reader.peek();
System.out.println("Name: "+reader.nextString() );
}
if("age".equals(fieldname)) {
//move to next token
token = reader.peek();
System.out.println("Age:" + reader.nextInt());
}
if("verified".equals(fieldname)) {
//move to next token
token = reader.peek();
System.out.println("Verified:" + reader.nextBoolean());
}
}
}
}
Output
Name: Mahesh Kumar
Age:21
Verified:false
Marks [ 100 90 85 100 14 95 ]
Your JSON has one tricky element - label arrays contain one-element JSON object. We can unwrap it using custom deserialiser. To do that let's create simple POJO structure which fit's JSON payload. JSON starts from [ so it means we need to parse it as an array. All elements have the same structure. We can define it like below:
class Phrase {
private String label;
private String domain;
#JsonAdapter(StringWrapperJsonDeserializer.class)
#SerializedName("synonymlabels")
private List<String> synonymLabels;
#JsonAdapter(StringWrapperJsonDeserializer.class)
#SerializedName("childrenlabels")
private List<String> childrenLabels;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public List<String> getSynonymLabels() {
return synonymLabels;
}
public void setSynonymLabels(List<String> synonymLabels) {
this.synonymLabels = synonymLabels;
}
public List<String> getChildrenLabels() {
return childrenLabels;
}
public void setChildrenLabels(List<String> childrenLabels) {
this.childrenLabels = childrenLabels;
}
#Override
public String toString() {
return "Phrase{" +
"label='" + label + '\'' +
", domain='" + domain + '\'' +
", synonymLabels=" + synonymLabels +
", childrenLabels=" + childrenLabels +
'}';
}
}
When we want to use another name for property in Java comparing to what we have in JSON we use SerializedName annotation. To inform Gson library that we would like to handle given element in a specific way we use JsonAdapter annotation. In case we do not know how to write custom deserialiser it is always safe to use Map<String, Object> type for unknown or random JSON object. In case we have list of objects we can use List<Map<String, Object>>. Let's write simple deserialiser for labels arrays:
class StringWrapperJsonDeserializer implements JsonDeserializer<List<String>> {
#Override
public List<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonArray()) {
final JsonArray array = (JsonArray) json;
final int size = array.size();
if (size == 0) {
return Collections.emptyList();
}
List<String> labels = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
JsonObject jsonElement = (JsonObject) array.get(i);
Set<String> keys = jsonElement.keySet();
for (String key : keys) {
labels.add(jsonElement.getAsJsonPrimitive(key).getAsString());
}
}
return labels;
}
return Collections.emptyList();
}
}
Algorithm is quite simple: if given element is an array, iterate over it and take each object one-by-one. For each object get all keys and add corresponding values to labels list which is our result of deserialisation process. Example usage, could look like this:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
public class GsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
Phrase[] phrases = gson.fromJson(new FileReader(jsonFile), Phrase[].class);
Stream.of(phrases).forEach(System.out::println);
}
}
Above code prints:
Phrase{label='Hospital', domain='Health_Care', synonymLabels=[SHCO, HCO], childrenLabels=[Childern_Hospital, Mental_Hospital, Heart_Hospital, Orthopadic_Hospital, General_Hospital, Gynac_Hospital, Cancer_Hospital, Burn_Hospital, Trauma_Care_Hospital]}
Phrase{label='Doctor', domain='Health_Care', synonymLabels=[Clinician, Physician, Medical_Practitioner], childrenLabels=[Cardiaologist, Allergist, Nurologist, Gynacologist, General_Physician, Anesthetist, Physiotherapist, Urologist, Oncologist, Homeopath, Dentist]}
Read also:
JsonAdapter
Serializing and Deserializing a List with Gson
I am currently having trouble trying to parse this VCAP_SERVICES to java objects. I do not quite understand how to structure the POJO to allow it to map the values from the json string. Can someone please help me structure my pojo so that it is aligns with the json string?
I want to create objects for both of the credentials: accessToken... jdbcurl.
VCAP_SERVICES
"VCAP_SERVICES": {
"user-provided": [
{
"credentials": {
"accessTokenUri": "tokenurl",
"apiUrl": "apiurl",
"clientId": "typeofID",
"clientSecret": "secretAf",
"scope": "none"
},
"syslog_drain_url": "",
"volume_mounts": [],
"label": "user-provided",
"name": "OAuth2",
"tags": []
},
{
"credentials": {
"jdbcUrl": "jdbc:oracle:connection[host]:[port]/service",
"spring.datasource.driver-class-name": "oracle.jdbc.OracleDriver",
"spring.datasource.initialize": "false"
},
"syslog_drain_url": "",
"volume_mounts": [],
"label": "user-provided",
"name": "Database",
"tags": []
}
]
Java Class
ObjectMapper mapper = new ObjectMapper();
//json String to Object
CupsProperties properties = mapper.readValue(VCAP_Services, CupsProperties.class);
System.out.println(properties.getJdbcUrl() + "!!!!!!!!!!!!!!!!!!!");
POJOS
public class UserProviderWrapper {
#JsonProperty("user-provided")
public List<CupsProperties> cupsProperties;
#JsonProperty("syslog_drain_url")
public String syslog_drain_url;
#JsonProperty("volume_mounts")
public List<String> volume_mounts;
#JsonProperty("label")
public String label;
#JsonProperty("name")
public String name;
#JsonProperty("tags")
public List<String> tags;
//getters and setters
public class CupsProperties {
#JsonProperty("jdbcUrl")
public String jdbcUrl;
#JsonProperty("spring.datasource.driver-class-name")
public String driver;
#JsonProperty("spring.datasource.initialize")
public String initialize;
//getters and setters
Error
Unrecognized field "user-provided" (class rest.springframework.model.CupsProperties), not marked as ignorable (2 known properties: "jdbcUrl", "dataSource"])
at [Source: {"user-provided":[{ "credentials": { "jdbcUrl": "jdbc:oracle:thin:user/pass//host:port/service", "spring.datasource.driver-class-name": "oracle.jdbc.OracleDriver", "spring.datasource.initialize": "false" }, "syslog_drain_url": "", "volume_mounts": [ ], "label": "user-provided", "name": "Oracle", "tags": [ ] }]}; line: 1, column: 19] (through reference chain: rest.springframework.model.CupsProperties["user-provided"])
Check below solution and see if it fulfills your need. You can build on to it if you need to parse more fields.
import java.util.Iterator;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
public class JsonParser {
public static void main(String[] args) {
String VCAP_Services = "{\"userProvided\": [{\"credentials\": {\"accessTokenUri\": \"tokenurl\",\"apiUrl\": \"apiurl\",\"clientId\": \"typeofID\",\"clientSecret\": \"secretAf\",\"scope\": \"none\"},\"syslog_drain_url\": \"\",\"volume_mounts\": [],\"label\": \"user-provided\",\"name\": \"OAuth2\",\"tags\": []},{\"credentials\": {\"jdbcUrl\": \"jdbc:oracle:connection[host]:[port]/service\",\"spring.datasource.driver-class-name\": \"oracle.jdbc.OracleDriver\",\"spring.datasource.initialize\": \"false\"},\"syslog_drain_url\": \"\",\"volume_mounts\": [],\"label\": \"user-provided\",\"name\": \"Database\",\"tags\": [] } ] } ";
CupsProperties properties=null;
try {
JSONParser jsonParser = new JSONParser();
JSONObject vcapServiceJSONObject = (JSONObject) jsonParser.parse(VCAP_Services);
for(Object key: vcapServiceJSONObject.keySet()){
String keyStr = (String) key;
JSONArray userProvidedList = (JSONArray) vcapServiceJSONObject.get(keyStr);
Iterator i = userProvidedList.iterator();
while (i.hasNext()) {
JSONObject innerObj = (JSONObject) i.next();
JSONObject credentialsObject = (JSONObject) innerObj.get("credentials");
if(credentialsObject.containsKey("jdbcUrl")){
//set to your pojo objects
System.out.println("JDBC url:" + credentialsObject.get("jdbcUrl"));
}
if(credentialsObject.containsKey("accessTokenUri")){
//set to your pojo objects
System.out.println("Access token URI:" + credentialsObject.get("accessTokenUri"));
}
}
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}
Output
Access token URI:tokenurl
JDBC url:jdbc:oracle:connection[host]:[port]/service
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.
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.