I am trying to deserialize a two things from JSON. The format of the first one is as follows:
String json = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
This is the second:
String json2 = "[{\"name\":\"Random\"," + "\"longitude\":-3.1, \"latitude\":55}]
My code is simple, and is as follows:
ObjectMapper mapper = new ObjectMapper();
var asArray = mapper.readValue(json, NoFlyZone[].class);
var asArray2 = mapper.readValue(json, LngLat.class);
The NoFlyZone class:
record NoFlyZone(LngLat[] coordinates) {
#JsonIgnoreProperties("name")
NoFlyZone (#JsonProperty("coordinates") double[][] coordinates) {
this(doubleArrayToLngLatArray(coordinates));
}
private static LngLat[] doubleArrayToLngLatArray(double[][] coordinates) {
var coordinateArray = new LngLat[coordinates.length];
for (int i = 0; i < coordinates.length; i++) {
coordinateArray[i] = new LngLat(coordinates[i][0], coordinates[i][1]);
}
System.out.println(coordinateArray);
return coordinateArray;
}
}
And finally, the LngLat class:
record LngLat(double lng, double lat) {
LngLat (#JsonProperty("longitude") double lng,
#JsonProperty("latitude") double lat) {
this.lng = lng;
this.lat = lat;
}
}
I have tried deserialising them in the way shown above, but a MismatchedInputException is thrown when trying to deserialize the first string, with the error message "Cannot deserialize value of type uk.ac.ed.inf.LngLat from Array value (token JsonToken.START_ARRAY)...". I'm not sure why this is happening, so any help would be appreciated.
I have also tried adding the annotation
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
and fixing it as detailed in Alexander's answer, but then the second string throws an error when attempting to be deserialised.
Since your record LngLat is represented as JSON-array (like "[-3.1, 55.4]") you need to customize its deserialization.
And for that you can use #JsonFormat annotation providing the attribute shape with the value of JsonFormat.Shape.ARRAY. That would instruct Jackson to populate the record properties from the array in order of their declaration.
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
record LngLat(double lng, double lat) {}
And enclosing record NoFlyZone would be simplified to (special method for parsing array of LngLat is redundant):
#JsonIgnoreProperties("name")
record NoFlyZone(LngLat[] coordinates) {}
Usage example:
String json = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, new TypeReference<List<NoFlyZone>>() {}));
Output:
note: toString() method of the NoFlyZone has been overridden to display the array correctly
[NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.4], LngLat[lng=-3.1, lat=55.9], LngLat[lng=-3.7, lat=55.3], LngLat[lng=-3.8, lat=55.7], LngLat[lng=-3.0, lat=55.8]]}]
Update
If you need to support the two entirely different structures of JSON, then you also need to customize deserialization on the NoFlyZone level (because its JSON-shape differs).
One of the ways to do that is to introduce the factory method annotated with #JsonCreator. It would expect a single argument of type Map<String, JsonNode> in order to be able to funnel all the properties thought it.
We also need to set the attribute of ignoreUnknown of #JsonIgnoreProperties to true.
Note: the definition of LngLat remains the same (as shown above annotated with #JsonFormat).
#JsonIgnoreProperties(ignoreUnknown = true)
public record NoFlyZone(LngLat[] coordinates) {
#JsonCreator
public static NoFlyZone getInstance(Map<String, JsonNode> fields) throws IOException {
boolean isArray = fields.containsKey("coordinates");
LngLat[] longLat;
if (isArray) {
ObjectReader reader = new ObjectMapper().readerFor(LngLat[].class);
longLat = reader.readValue(fields.get("coordinates")); // parsing JsonNode which corresponds to "coordinates" property
} else {
longLat = new LngLat[] { // creating a single-element array
new LngLat(
Double.parseDouble(fields.get("longitude").toString()),
Double.parseDouble(fields.get("latitude").toString())
)
};
}
return new NoFlyZone(longLat);
}
// toString()
}
Usage example:
String json1 = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
String json2 = "[{\"name\":\"Random\"," + "\"longitude\":-3.1, \"latitude\":55}]";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json1, new TypeReference<List<NoFlyZone>>() {}));
System.out.println(mapper.readValue(json2, new TypeReference<List<NoFlyZone>>() {}));
Output:
[NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.4], LngLat[lng=-3.1, lat=55.9], LngLat[lng=-3.7, lat=55.3], LngLat[lng=-3.8, lat=55.7], LngLat[lng=-3.0, lat=55.8]]}]
[NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.0]]}]
Related
I have a for loop which iterates and generates key value pairs for different employees.
I need to create a JSON array like below and write it to a JSON file at the end.
I am having trouble figuring out the ideal way to code it (JSON Objects -> JSON Array -> JSON file?).
I am open to use json-simple/GSON.
Desired JSON file format:
[
{
"employeeFirstName": "Mark",
"employeeLastName": "Williams",
"employeeDepartment": "Sales",
},
{
"employeeFirstName": "John",
"employeeLastName": "Carpenter",
"employeeDepartment": "Accounts",
},
{
"employeeFirstName": "David",
"employeeLastName": "Hunter",
"employeeDepartment": "Marketing",
},
]
I tried using a JSONObject and add it to a JSONArray. But, couldn't figure how to code it for iterations.
My current Java class:
public class Test {
public void createEmployeesJSONArrayFile(ITestContext iTestContext) {
for (ITestResult testResult : iTestContext.getFailedTests().getAllResults()) {
System.out.println("employeeFirstName: " + testResult.getEmployeeFirstName()));
System.out.println("employeeLastName: " + testResult.getEmployeeLastName());
System.out.println("employeeDepartment: " + testResult.getEmployeeDepartment());
}
}
}
What is the simplest or ideal way to achieve this?
A simple way to achieve this would be to use Gson, an API provided by Google. You could write the Collection of ITestResult objects to a file. The toJson function will take the Collection of ITestResult objects and write them to the the given Appenable object, which in this case is a BufferedWriter which points to a file.
(untested, one sec, not at workstation)
Collection<ITestResult> results = iTestContext.getFailedTests().getAllResults();
new GsonBuilder()
.create()
.toJson(results, Files.newBufferedWriter(Paths.get("path", "to", "file")));
If your goal is to write to file eventually, you can also use jackson apis.
ObjectMapper mapper = new ObjectMapper();
//To add indentation to output json
mapper.enable(SerializationFeature.INDENT_OUTPUT);
Collection<ITestResult> results = iTestContext.getFailedTests().getAllResults();
try{
mapper.writeValue(new File("/somepath/output.json"), results);
catch (IOException){
e.printStackTrace();
}
Note: Recommended to use single instance of object mapper
For following snippet:
public static final class Node {
class Employee {
private final String employeeFirstName;
private final String employeeLastName;
private final String employeeDepartment;
public Employee(String employeeFirstName, String employeeLastName, String employeeDepartment) {
this.employeeFirstName = employeeFirstName;
this.employeeLastName = employeeLastName;
this.employeeDepartment = employeeDepartment;
}
}
List<Employee> employees = Arrays.asList(
new Employee("Mark", "Williams", "Sales"),
new Employee("John", "Carpenter", "Accounts"),
new Employee("David", "Hunter", "Marketing"));
// String json = ...
}
Using gson-utils
String json = GsonUtils.writeValue(data);
Using jackson-utils
String json = JacksonUtils.writeValue(data);
I want to convert each integer/double value to String present in json request before storing in MongoDB database.
There can be multiple fields like amountValue in the json. I am looking for a generic way which can parse json with any number of such attributes value to string. My request will have around 200 fields.
ex: "amountValue": 200.00, to "amountValue": "200.00",
{
"templateName": "My DC Template 14",
"templateDetails": {
"beneficiaryName": "Snow2",
"dcOpenAmount": {
"amountValue": 200.00,
}
}
}
My mongoDB Document is of the form
#Document
public class TemplateDetails {
#Id
private long templateId;
private String templateName;
private Object templateDetail;
}
Because we are storing document in mongodb as an object(Which can accept any type of json request) we dont have field level control on it.
In my controller, converting the request object to json.
This is how I tried. But its not meeting my expectation. It is still keeping the amount value to its original double form.:
ObjectMapper mapper = new ObjectMapper();
try {
String json = mapper.writeValueAsString(templateRequestVO);
System.out.println("ResultingJSONstring = " + json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
Output :
ResultingJSONstring = {"id":null,"userId":"FU.ZONKO","txnType":"LCI","accessIndicator":"Public","templateId":null,"templateName":"My DC Template 14","tags":null,"templateDetails":{"applicantDetail":{"applicantName":"Tom","applicantAddress":{"addressLine1":"Infosys, Phase 2","city":"PUNE","state":"MAHARASHTRA","country":"INDIA","zip":"40039"},"accountId":"Account1234","customerId":"JPMORGAN"},"beneficiaryName":"Snow2","dcOpenAmount":{"amountValue":200.0,"currency":"USD"}}}
Is there any way to accomplish the result ? Or anything which can help to store documents in mongodb with attribute type as String ?
You can use Json manipulation avaliable in "org.json.JSONObject" to convert Double value to Stirng .
If your Json structure won't change and will remain as said above , you can do the following.
import org.json.JSONObject;
public static void main(String args[]) {
String j = "{ \"templateName\": \"My DC Template 14\", \"templateDetails\": { \"beneficiaryName\": \"Snow2\", \"dcOpenAmount\": { \"amountValue\": 200.00 } } }";
JSONObject jo = new JSONObject(j);
jo.getJSONObject("templateDetails")
.getJSONObject("dcOpenAmount")
.put("amountValue", String.valueOf(jo.getJSONObject("templateDetails").getJSONObject("dcOpenAmount").getDouble("amountValue")));
System.out.println(jo.toString());
}
Following will be the output
{"templateDetails":{"dcOpenAmount":{"amountValue":"200.0"},"beneficiaryName":"Snow2"},"templateName":"My DC Template 14"}
I don't know for mongodb but for a json string you can replace them with a regex and the function replace like this :
public class Test {
public static void main(String[] args) {
String json = "{\"id\":null,\"userId\":\"FU.ZONKO\",\"txnType\":\"LCI\",\"accessIndicator\":\"Public\",\"templateId\":null,\"templateName\":\"My DC Template 14\",\"tags\":null,\"templateDetails\":{\"applicantDetail\":{\"applicantName\":\"Tom\",\"applicantAddress\":{\"addressLine1\":\"Infosys, Phase 2\",\"city\":\"PUNE\",\"state\":\"MAHARASHTRA\",\"country\":\"INDIA\",\"zip\":\"40039\"},\"accountId\":\"Account1234\",\"customerId\":\"JPMORGAN\"},\"beneficiaryName\":\"Snow2\",\"dcOpenAmount\":{\"amountValue\":200.0,\"currency\":\"USD\"}}}";
System.out.println(replaceNumberByStrings(json));
}
public static String replaceNumberByStrings(String str){
return str.replaceAll("(?<=:)\\d+(\\.\\d+)?(?=(,|}))","\"$0\"");
}
}
It will look for all fields with a numeric value in the json string and add quotes to the value. This way they will be interpreted as strings when the json willl be parsed.
It will not work if the value is in an array though, but in this case it should not be a problem.
I'm trying to parse the below Json using the Gson lib in Java. When using other languages, such as C#, this JSON is parsed into an array, however it seems Gson converts this into a set of java attributes (which to be honest, makes more sense to me). Does anyone know if I can change this behaviour of the Gson lib?
{
"Outer": {
"0": {
"Attr1": 12345,
"Attr2": 67890
},
"1": {
"Attr1": 54321,
"Attr2": 09876
}
}
}
The below code demonstrates how Gson parses the array as a JsonObject. To be clear, I realise I've referenced outer as a JsonObject but I was just doing this to demonstrate the code. If I try and reference outer as an JsonArray, the code fails.
String json = "{\"Outer\": { \"0\": { \"Attr1\": 12345, \"Attr2\": 67890 }, \"1\": { \"Attr1\": 54321, \"Attr2\": 09876 }}}";
Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.setLenient()
.serializeNulls()
.create();
JsonObject jo = gson.fromJson(json, JsonObject.class);
JsonObject outer = jo.getAsJsonObject("Outer");
System.out.println(outer);
System.out.println(outer.isJsonArray());
Result:
{"0":{"Attr1":12345,"Attr2":67890},"1":{"Attr1":54321,"Attr2":"09876"}}
false
//edit
I'm using this current simple Json as an example, however my application of this code will be to parse Json that's of varying and unknown shape. I therefore need Gson to automatically parse this to an array so that the isJsonArray returns true.
TL;DR: See "Using Deserializer" section at the bottom for parsing straight to array.
That JSON does not contain any arrays. An array would use the [...] JSON syntax.
Normally, a JSON object would map to a POJO, with the name in the name/value pairs mapping to a field of the POJO.
However, a JSON object can also be mapped to a Map, which is especially useful when the names are dynamic, since POJO fields are static.
Using Map
The JSON object with numeric values as names can be mapped to a Map<Integer, ?>, e.g. to parse that JSON to POJOs, do it like this:
class Root {
#SerializedName("Outer")
public Map<Integer, Outer> outer;
#Override
public String toString() {
return "Root[outer=" + this.outer + "]";
}
}
class Outer {
#SerializedName("Attr1")
public int attr1;
#SerializedName("Attr2")
public int attr2;
#Override
public String toString() {
return "Outer[attr1=" + this.attr1 + ", attr2=" + this.attr2 + "]";
}
}
Test
Gson gson = new GsonBuilder().create();
Root root;
try (BufferedReader in = Files.newBufferedReader(Paths.get("test.json"))) {
root = gson.fromJson(in, Root.class);
}
System.out.println(root);
Output
Root[outer={0=Outer[attr1=12345, attr2=67890], 1=Outer[attr1=54321, attr2=9876]}]
Get as Array
You can then add a helper method to the Root class to get that as an array:
public Outer[] getOuterAsArray() {
if (this.outer == null)
return null;
if (this.outer.isEmpty())
return new Outer[0];
int maxKey = this.outer.keySet().stream().mapToInt(Integer::intValue).max().getAsInt();
Outer[] arr = new Outer[maxKey + 1];
this.outer.forEach((k, v) -> arr[k] = v);
return arr;
}
Test
System.out.println(Arrays.toString(root.getOuterAsArray()));
Output
[Outer[attr1=12345, attr2=67890], Outer[attr1=54321, attr2=9876]]
Using Deserializer
However, it would likely be more useful if the conversion to array is done while parsing, so you need to write a JsonDeserializer and tell Gson about it using #JsonAdapter:
class Root {
#SerializedName("Outer")
#JsonAdapter(OuterArrayDeserializer.class)
public Outer[] outer;
#Override
public String toString() {
return "Root[outer=" + Arrays.toString(this.outer) + "]";
}
}
class OuterArrayDeserializer implements JsonDeserializer<Outer[]> {
#Override
public Outer[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// Parse JSON array normally
if (json.isJsonArray())
return context.deserialize(json, Outer[].class);
// Parse JSON object using names as array indexes
JsonObject obj = json.getAsJsonObject();
if (obj.size() == 0)
return new Outer[0];
int maxKey = obj.keySet().stream().mapToInt(Integer::parseInt).max().getAsInt();
Outer[] arr = new Outer[maxKey + 1];
for (Entry<String, JsonElement> e : obj.entrySet())
arr[Integer.parseInt(e.getKey())] = context.deserialize(e.getValue(), Outer.class);
return arr;
}
}
Same Outer class and test code as above.
Output
Root[outer=[Outer[attr1=12345, attr2=67890], Outer[attr1=54321, attr2=9876]]]
I'll asume your JsonObject is a POJO class such like:
public Inner[] outer;
If you want an array of objects you can change your code to:
Inner[] jo = gson.fromJson(json, Inner[].class);
Jackson – Marshall String to JsonNode will be useful in your case.with following pom:-
//POM FILE
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
//JAVA CODE
//read json file data to String
byte[] jsonData = Files.readAllBytes(Paths.get("employee.txt"));
//create ObjectMapper instance
ObjectMapper objectMapper = new ObjectMapper();
//read JSON like DOM Parser
JsonNode rootNode = objectMapper.readTree(jsonData);
JsonNode idNode = rootNode.path("id");
System.out.println("id = "+idNode.asInt());
JsonNode phoneNosNode = rootNode.path("phoneNumbers");
Iterator<JsonNode> elements = phoneNosNode.elements();
while(elements.hasNext()){
JsonNode phone = elements.next();
System.out.println("Phone No = "+phone.asLong());
}
You can use the JsonNode class's method findParent findValue and findPath which reduce your code as compare to another parsing library.
Please refer below code
1.To get an array of Objects (outerArray)
2.You can extract a JsonArray (outerJsonArray) containing values of inner objects in Outer (in case keys aren't significant for further use)
String json = "{\"Outer\": { \"0\": { \"Attr1\": 12345, \"Attr2\": 67890 }, \"1\": { \"Attr1\": 54321, \"Attr2\": 09876 }}}";
Gson gson = new GsonBuilder().disableHtmlEscaping().setLenient().serializeNulls().create();
JsonObject jo = gson.fromJson(json, JsonObject.class);
JsonObject outer = jo.getAsJsonObject("Outer");
Object[] outerArray = outer.entrySet().toArray();
// outerArray: [0={"Attr1":12345,"Attr2":67890}, 1={"Attr1":54321,"Attr2":"09876"}]
JsonArray outerJsonArray = new JsonArray();
outer.keySet().stream().forEach(key -> {
outerJsonArray.add(outer.get(key));
});
//jsonArray=[{"Attr1":12345,"Attr2":67890},{"Attr1":54321,"Attr2":"09876"}]
System.out.println(outer);
System.out.println(outerJsonArray.isJsonArray() + " " + outerJsonArray);
I'm pretty new to world of jackson, and wanted to read the value of specific field from list of jsons (which is a response body of third-party api).
for a single json, using objectMapper works fine.
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(sampleString);
JsonNode idNode = rootNode.path("id");
System.out.println("id: "+ idNode.asText());
But I want to parse list of jsons (which is a string coming from a response body)
.So for example I receive this body:
[
{
"id":10,
"firstName":"Jack",
"primaryPhone":"9999999999",
"email":"jack#me.com"
},
{
"id":4,
"firstName":"Mark",
"primaryPhone":"9999999991",
"email":"mark#me.com"
},
{
"id":12,
"firstName":"Susaan",
"primaryPhone":"9999999992",
"email":"susan23#me.com"
}
]
I want to read the ids first, and if I find a specific id, return some other info from that block.
For example if id=4, read the firstName and email of that person.
But I'm not sure how to parsee list of json.
Any suggestions/comments is appreciated.
You can try,
JsonNode array = objectMapper.readValue(sampleString, JsonNode.class);
for(int i=0;i<array.length;i++){
JsonNode jsonNode = array.get(i);
JsonNode idNode = jsonNode.get("id");
String id = idNode.asText();
if(id.equals("4")){
JsonNode firstNameNode = jsonNode.get("firstName");
String firstName = firstNameNode.asText();
System.out.println("firstName = " + firstName);
JsonNode emailNode = jsonNode.get("email");
String email = emailNode.asText();
System.out.println("email = " + email);
break;
}
}
You can use Json Path.
So, the query would be something like this:
$[?(#.id == 4)].firstName
You can create a POJO like the one below:
class Record {
private Long id;
private String firstName;
//Getters and setters
}
And deserialise the json into List of these POJOS, e.g.:
ObjectMapper mapper = new ObjectMapper();
List<Record> records = mapper.readValue("", new TypeReference<List<Record>>() { });
Once done, you can filter out the records with stream, e.g.:
List<Record> filtered = records.stream()
.filter(r -> r.getId() = 12)
.collect(Collectors.toList());
I use Jackson to deserialize JSON to an immutable custom Java object. Here is the class:
final class DataPoint {
private final int count;
private final double mean;
#JsonCreator
DataPoint(
#JsonProperty("count") int count,
#JsonProperty("mean") double mean) {
if (count <= 0) {
throw new IllegalArgumentException("...");
}
this.count = count;
this.mean = mean;
}
// getters...
}
Here is the JSON I deserialize:
{
"count": 1,
"mean": 2
}
It works fine. Now I break the JSON, i.e. remove one proprerty:
{
"count": 1
}
The deserialization code is:
String json = "..."; // the second JSON
ObjectMapper mapper = new ObjectMapper();
DataPoint data = mapper.readValue(json, DataPoint.class);
Now I get count == 1, and mean == 0.0. Instead, I would like the Jackson to throw an exception, sine one of the required field is missing in the JSON. How can I archive that?
Thank you a lot, guys!
Since you're using a constructor, you can enable DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES to throw an exception on missing properties:
String json = "..."; // the second JSON
ObjectMapper mapper = new ObjectMapper()
.enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES);
DataPoint data = mapper.readValue(json, DataPoint.class);