How to deal with null received from MongoDB using SpringBoot? - json

I am using Java 17, Spring Boot 2.7 with MongoDB with MongoTemplate. But the data I am receiving are filled with null. Is the problem arises due to conversion from bson to json ? Or something else which I overlooked.
My UI Class:
#Getter
public class LvUi {
private final String employeeCode;
public LvUi(
#Size(max = 5, message = "Employee Code Must Be Within 4 To 5 Character Long Or Blank")
#JsonProperty("employeeCode") String employeeCode) {
this.employeeCode = employeeCode;
}
}
My Record Class:
public record Leave(
String employeeCode,String leaveType,
LocalDate startDate, LocalDate endDate
) {
}
My Repository:
public interface AttendanceRepo {
List<Leave> selectLeaveApplication(LvUi lvUi);
}
#Repository("attendanceMongo")
public class AttendanceRepoMongo implements AttendanceRepo {
private final MongoTemplate mongoTemplate;
#Autowired
public AttendanceRepoMongo(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Override
public List<Leave> selectLeaveApplication(LvUi lvUi) {
final Query query = new Query();
query.addCriteria(Criteria.where("emp_cd")
.is(lvUi.getEmployeeCode()));
query.fields()
.include("emp_cd","leave_type","leave_start","leave_end");
return new ArrayList<>(mongoTemplate.find(query, Leave.class, "leave"));
}
}
My Controller:
#RestController
#RequestMapping("hr/")
public class AttendanceApi {
private final AttendanceService attendanceService;
#Autowired
public AttendanceApi(AttendanceService attendanceService) {
this.attendanceService = attendanceService;
}
#PostMapping("leave/application/pre")
public List<Leave> leaveApplicationPre(
#Valid #NotNull #RequestBody LvUi lvUi) {
return attendanceService.selectLeaveApplication(lvUi);
}
}
My Service:
public interface AttendanceService {
List<Leave> selectLeaveApplication(LvUi lvUi);
}
#Service("attendanceService")
public class AttendanceServiceImpl implements AttendanceService {
private final AttendanceRepo attendanceRepo;
#Autowired
public AttendanceServiceImpl(
#Qualifier("attendanceMongo") AttendanceRepo attendanceRepo) {
this.attendanceRepo = attendanceRepo;
}
#Override
public List<Leave> selectLeaveApplication(LvUi lvUi) {
return attendanceRepo.selectLeaveApplication(lvUi);
}
}
My Config:
#Configuration
#AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class ErpConfig {
#Bean
public MongoTemplate mongoTemplate() {
final MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017");
return new MongoTemplate(mongoClient, "erpdb");
}
}
My Data:
[
{
"_id": ObjectId("62ac28826940f44772b94b64"),
"emp_cd": "A001",
"appl_no": 59,
"leave_type": "CL",
"leave_start": ISODate("2022-05-09T00:00:00Z"),
"leave_end": ISODate("2022-05-09T00:00:00Z")
},
{
"_id": ObjectId("62ac28826940f55772b94b64"),
"emp_cd": "A002",
"appl_no": 69,
"leave_type": "EL",
"leave_start": ISODate("2022-05-09T00:00:00Z"),
"leave_end": ISODate("2022-05-09T00:00:00Z")
},
{
"_id": ObjectId("62ac28826940f66772b94b64"),
"emp_cd": "A002",
"appl_no": 79,
"leave_type": "EL",
"leave_start": ISODate("2022-06-09T00:00:00Z"),
"leave_end": ISODate("2022-06-09T00:00:00Z")
}
]
My Input (Through Postman):
POST
http://localhost:8080/hr/leave/application/pre
{
"employeeCode": "A002"
}
My Output (Through Postman):
[
{
"emp_cd": null,
"leave_type": null,
"leave_start": null,
"leave_end": null
},
{
"emp_cd": null,
"leave_type": null,
"leave_start": null,
"leave_end": null
}
]
If I add the following code in my Repository
final FindIterable<Document> documents =
mongoTemplate.getCollection("leave").find();
List<Document> res = new ArrayList<>();
for (Document document : documents)
res.add(document);
res.forEach(System.out::println);
The Result :
Document{{_id=62ac28826940f44772b94b64, emp_cd=A001, appl_no=59, leave_type=CL, leave_start=Mon May 09 05:30:00 IST 2022, leave_end=Mon May 09 05:30:00 IST 2022}}
Document{{_id=62ac28826940f55772b94b64, emp_cd=A002, appl_no=69, leave_type=EL, leave_start=Mon May 09 05:30:00 IST 2022, leave_end=Mon May 09 05:30:00 IST 2022}}
Document{{_id=62ac28826940f66772b94b64, emp_cd=A002, appl_no=79, leave_type=EL, leave_start=Sat Jun 09 05:30:00 IST 2022, leave_end=Sat Jun 09 05:30:00 IST 2022}}
Please help.

Related

getting error : Data provider mismatch Method: loginTest

Please help me to resolve this issue. I am trying to extract data from Json using data provider for one of my test case. I have done the below code.
This below method is in base class :
public List<HashMap<String, String>> getJsonDataToMap(String filePath) throws IOException {
String jsonContent = FileUtils.readFileToString(new File(filePath), StandardCharsets.UTF_8);
ObjectMapper mapper = new ObjectMapper();
List<HashMap<String, String>> data = mapper.readValue(jsonContent, new TypeReference<List<HashMap<String, String>>>() {
});
return data;
}
Now Login.Test class is extending Base class and i have created these methods to extract the data from json and use in in test case :
public class LoginTest extends BaseClass {
public static String dataPath="src/test/java/com/automationqa/testData/LoginData.json";
#DataProvider(name = "testdatalogin")
public Object[][] getData() throws IOException {
List<HashMap<String, String>> data = getJsonDataToMap(dataPath);
return new Object[][] {{data.get(0), data.get(1)}};
}
#Test(dataProvider = "testdatalogin")
public void loginTest(HashMap<String, String> input) {
loginPage.enterUserName(input.get("userid"));
loginPage.enterPassword(input.get("password"));
loginPage.clickOnLogin();
String pageTitle = driver.getTitle();
Assertions.assertThat(pageTitle).isEqualTo("Guru99 Bank Manager HomePage");
}
JSon file :
[
{
"userid": "mngr452488",
"password": "davysav"
},
{
"userid": "mgr45248xyz",
"password": "heavyset"
}
]
but getting below error:
org.testng.internal.reflect.MethodMatcherException:
Data provider mismatch
Method: loginTest([Parameter{index=0, type=java.util.HashMap, declaredAnnotations=[]}])
Arguments: [(java.util.HashMap) {password=davysav, userid=mngr452488},(java.util.HashMap) {password=heavyset, userid=mgr45248xyz}]
at org.testng.internal.reflect.DataProviderMethodMatcher.getConformingArguments(DataProviderMethodMatcher.java:45)
at org.testng.internal.Parameters.injectParameters(Parameters.java:796)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:983)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
at org.testng.TestRunner.privateRun(TestRunner.java:648)
at org.testng.TestRunner.run(TestRunner.java:505)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
at org.testng.SuiteRunner.run(SuiteRunner.java:364)
JSon file :
[
{
"userid": "mngr452488",
"password": "davysav"
},
{
"userid": "mgr45248xyz",
"password": "heavyset"
}
]
but getting below error:
org.testng.internal.reflect.MethodMatcherException:
Data provider mismatch
Method: loginTest([Parameter{index=0, type=java.util.HashMap, declaredAnnotations=[]}])
Arguments: [(java.util.HashMap) {password=davysav, userid=mngr452488},(java.util.HashMap) {password=heavyset, userid=mgr45248xyz}]
at org.testng.internal.reflect.DataProviderMethodMatcher.getConformingArguments(DataProviderMethodMatcher.java:45)
at org.testng.internal.Parameters.injectParameters(Parameters.java:796)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:983)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
at org.testng.TestRunner.privateRun(TestRunner.java:648)
at org.testng.TestRunner.run(TestRunner.java:505)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
at org.testng.SuiteRunner.run(SuiteRunner.java:364)
Your provider provides two arguments for each iteration but test method takes only one.
I think you want to do something like
#DataProvider(name = "testdatalogin")
public Object[][] getData() throws IOException {
List<Map<String, String>> data = getJsonDataToMap(dataPath);
return new Object[][] {{data.get(0)}, {data.get(1)}};
}

Spring WebFlux - Add a wrapping class before serialization

I'm developing APIs for an exam project, but I wanted their responses to be consistently using a wrapping class on all of them (Telegram Bot API style for those who know them).
So, for example, having these two classes:
public class User {
public int id;
public String name;
}
public class Item {
public int id;
public String itemName;
public User owner;
}
What Spring returns to me is this output:
{
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}
What I want instead is for this output to be returned:
{
"ok": true,
"data": {
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}
}
Maybe using a class wrapper like this:
public class ResponseWrapper<T> {
public boolean ok;
public T data;
}
Is it possible to do this?
I understand you need a global setting to convert all your responses into a standard one. For this you can implement ResponseBodyAdvice and have a common structure for all your api responses. Refer this link for a detailed example
Edit: For spring-webflux you can extend ResponseBodyResultHandler and override handleResult. An example is given in this answer
I thank #JustinMathew for the help, at the end, in my case (using Spring WebFlux with Kotlin), the ResponseBodyResultHandler class was more useful to me.
// File: /MicroserviceApplication.kt
#SpringBootApplication
class MicroserviceApplication {
#Autowired
lateinit var serverCodecConfigurer: ServerCodecConfigurer
#Autowired
lateinit var requestedContentTypeResolver: RequestedContentTypeResolver
#Bean
fun responseWrapper(): ResponseWrapper = ResponseWrapper(
serverCodecConfigurer.writers, requestedContentTypeResolver
)
}
// File: /wrapper/model/Response.kt
data class Response<T>(
val ok: Boolean,
val data: T?,
val error: Error? = null
) {
data class Error(
val value: HttpStatus,
val message: String?
)
}
// File: /wrapper/ResponseWrapper.kt
class ResponseWrapper(writers: List<HttpMessageWriter<*>>, resolver: RequestedContentTypeResolver) :
ResponseBodyResultHandler(writers, resolver) {
override fun supports(result: HandlerResult): Boolean =
(result.returnType.resolve() == Mono::class.java)
|| (result.returnType.resolve() == Flux::class.java)
#Throws(ClassCastException::class)
override fun handleResult(exchange: ServerWebExchange, result: HandlerResult): Mono<Void> {
val body = when (val value = result.returnValue) {
is Mono<*> -> value
is Flux<*> -> value.collectList()
else -> throw ClassCastException("The \"body\" should be Mono<*> or Flux<*>!")
}
.map { r -> Response(true, r, null) }
.onErrorMap { e ->
if (e !is Response.Error)
Response.Error(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error")
else e
}
.onErrorResume { e -> Mono.just(Response(false, null, e as Response.Error)) }
return writeBody(body, returnType, exchange)
}
companion object {
#JvmStatic
private fun methodForReturnType(): Mono<Response<Any>>? = null
private val returnType: MethodParameter = MethodParameter(
ResponseWrapper::class.java.getDeclaredMethod("methodForReturnType"), -1
)
}
Edit: I made of this answer a library for Spring WebFlux 2.7.3 here.
P.S. I also took a cue from this other question, which faces the same problem but with Java.

GraphQL java send custom error in json format

I am working in an graphql application where I have to send custom error object / message in json irrespective of whether it occurs in servlet or service.
Expected error response
{ errorCode: 400 //error goes here,
errorMessage: "my error mesage"}
It will be helpful if someone could guide me to achieve the above requirement.
GraphQL specification defines a clear format for the error entry in the response.
According to the spec, it should like this (assuming JSON format is used):
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
"extensions": {/* You can place data in any format here */}
}
]
So you won't find a GraphQL implementation that allows you to extend it and return some like this in the GraphQL execution result, for example:
"errors": [
{
"errorMessage": "Name for character with ID 1002 could not be fetched.",
"errorCode": 404
}
]
However, the spec lets you add data in whatever format in the extension entry. So you could create a custom Exception on the server side and end up with a response that looks like this in JSON:
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
"extensions": {
"errorMessage": "Name for character with ID 1002 could not be fetched.",
"errorCode": 404
}
}
]
It's quite easy to implement this on GraphQL Java, as described in the docs. You can create a custom exception that overrides the getExtensions method and create a map inside the implementation that will then be used to build the content of extensions:
public class CustomException extends RuntimeException implements GraphQLError {
private final int errorCode;
public CustomException(int errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
}
#Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new LinkedHashMap<>();
customAttributes.put("errorCode", this.errorCode);
customAttributes.put("errorMessage", this.getMessage());
return customAttributes;
}
#Override
public List<SourceLocation> getLocations() {
return null;
}
#Override
public ErrorType getErrorType() {
return null;
}
}
then you can throw the exception passing in the code and message from inside your data fetchers:
throw new CustomException(400, "A custom error message");
Now, there is another way to tackle this.
Assuming you are working on a Web application, you can return errors (and data, for that matter) in whatever format that you want. Although that is a bit awkward in my opinion. GraphQL clients, like Apollo, adhere to the spec, so why would you want to return a response on any other format? But anyway, there are lots of different requirements out there.
Once you get a hold of an ExecutionResult, you can create a map or object in whatever format you want, serialise that as JSON and return this over HTTP.
Map<String, Object> result = new HashMap<>();
result.put("data", executionResult.getData());
List<Map<String, Object>> errors = executionResult.getErrors()
.stream()
.map(error -> {
Map<String, Object> errorMap = new HashMap<>();
errorMap.put("errorMessage", error.getMessage());
errorMap.put("errorCode", 404); // get the code somehow from the error object
return errorMap;
})
.collect(toList());
result.put("errors", errors);
// Serialize "result" and return that.
But again, having a response that doesn't comply with the spec doesn't make sense in most of the cases.
The other posted answer didn't work for me.
I found a solution by creating the following classes:
1) A throwable CustomException of GraphQLError type (just like mentioned in another answer).
2) Creating a GraphQLError Adaptor, which is not a Throwable.
3) A custom GraphQLErrorHandler to filter the custom exception.
Step 1:
The below throwable CustomGraphQLException implements GraphQLError because the GraphQLErrorHandler interface accepts errors only of type GraphQLError.
public class CustomGraphQLException extends RuntimeException implements GraphQLError {
private final int errorCode;
private final String errorMessage;
public CustomGraphQLException(int errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
#Override
public List<SourceLocation> getLocations() {
return null;
}
#Override
public ErrorType getErrorType() {
return null;
}
#Override
public String getMessage() {
return this.errorMessage;
}
#Override
public Map<String, Object> getExtensions() {
Map<String, Object> customAttributes = new HashMap<>();
customAttributes.put("errorCode", this.errorCode);
customAttributes.put("errorMessage", this.getMessage());
return customAttributes;
}
}
Step 2:
A non-throwable adaptor of GraphQLError is created to avoid the stack-trace of the above custom exception being passed in the final GraphQL Error Response.
public class GraphQLErrorAdaptor implements GraphQLError {
private final GraphQLError graphQLError;
public GraphQLErrorAdaptor(GraphQLError graphQLError) {
this.graphQLError = graphQLError;
}
#Override
public List<SourceLocation> getLocations() {
return graphQLError.getLocations();
}
#Override
public ErrorType getErrorType() {
return graphQLError.getErrorType();
}
#Override
public String getMessage() {
return graphQLError.getMessage();
}
#Override
public Map<String, Object> getExtensions() {
return graphQLError.getExtensions();
}
}
Step 3:
A custom GraphQLErrorHandler is implemented to filter the custom CustomGraphQLException and avoid its replacement with the default graphQL error response.
public class CustomGraphQLErrorHandler implements GraphQLErrorHandler {
public CustomGraphQLErrorHandler() { }
public List<GraphQLError> processErrors(List<GraphQLError> errors) {
List<GraphQLError> clientErrors = this.filterGraphQLErrors(errors);
List<GraphQLError> internalErrors = errors.stream()
.filter(e -> isInternalError(e))
.map(GraphQLErrorAdaptor::new)
.collect(Collectors.toList());
if (clientErrors.size() + internalErrors.size() < errors.size()) {
clientErrors.add(new GenericGraphQLError("Internal Server Error(s) while executing query"));
errors.stream().filter((error) -> !this.isClientError(error)
).forEach((error) -> {
if (error instanceof Throwable) {
LOG.error("Error executing query!", (Throwable) error);
} else {
LOG.error("Error executing query ({}): {}", error.getClass().getSimpleName(), error.getMessage());
}
});
}
List<GraphQLError> finalErrors = new ArrayList<>();
finalErrors.addAll(clientErrors);
finalErrors.addAll(internalErrors);
return finalErrors;
}
protected List<GraphQLError> filterGraphQLErrors(List<GraphQLError> errors) {
return errors.stream().filter(this::isClientError).collect(Collectors.toList());
}
protected boolean isClientError(GraphQLError error) {
return !(error instanceof ExceptionWhileDataFetching) && !(error instanceof Throwable);
}
protected boolean isInternalError(GraphQLError error) {
return (error instanceof ExceptionWhileDataFetching) &&
(((ExceptionWhileDataFetching) error).getException() instanceof CustomGraphQLException);
}
}
Step 4:
Configure the CustomGraphQLErrorHandler in GraphQLServlet. I am assuming you are using spring-boot for this step.
#Configuration
public class GraphQLConfig {
#Bean
public ServletRegistrationBean graphQLServletRegistrationBean(
QueryResolver queryResolver,
CustomGraphQLErrorHandler customGraphQLErrorHandler) throws Exception {
GraphQLSchema schema = SchemaParser.newParser()
.schemaString(IOUtils.resourceToString("/library.graphqls", Charset.forName("UTF-8")))
.resolvers(queryResolver)
.build()
.makeExecutableSchema();
return new ServletRegistrationBean(new SimpleGraphQLServlet(schema,
new DefaultExecutionStrategyProvider(), null, null, null,
customGraphQLErrorHandler, new DefaultGraphQLContextBuilder(), null,
null), "/graphql");
}
}
Reference

De-deserializing JSON using Gson

I'm looking for help in de-serializing a JSON to an instance of its POJO. The top level POJO Graph.java has an attribute of type HashMap. While serializing it throws
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line n column nn path
$.degreesCountMap[0]
I know exactly what it means and how to fix it for for a top level collection but not sure how to specify the Type for an attribute of a another object.
I did review discussions on such issues in this and many other forums but I don't really see an answer that can help me.
I would greatly appreciate any help on this.
Here's the JSON of Graph:
{
"nodeCount":3,
"edgeCount":2,
"degreesCountMap":[
{
"ONE":2
},
{
"TWO":1
}
],
"nodes":[
{
"index":0,
"connectedIndices":[
1
]
},
{
"index":1,
"connectedIndices":[
0,
2
]
},
{
"index":2,
"connectedIndices":[
1
]
}
]
}
Here are the POJOs
Graph.java
public class Graph {
private HashMap<Degree, Integer> degreesCountMap;
private Integer edgeCount;
private Integer nodeCount;
private ArrayList<Node> nodes;
public HashMap<Degree, Integer> getDegreesCountMap() {
return degreesCountMap;
}
public void setDegreesCountMap(HashMap<Degree, Integer> degreesCountMap) {
this.degreesCountMap = degreesCountMap;
}
public void setNodes(ArrayList<Node> nodes) {
this.nodes = nodes;
}
}
Degree.java
public enum Degree {
ZERO, ONE, THREE, FOUR;
}
Node.java
public class Node {
private ArrayList<Integer> connectedIndices;
private int index;
public ArrayList<Integer> getConnectedIndices() {
return connectedIndices;
}
public int getIndex() {
return index;
}
public void setConnectedIndices(ArrayList<Integer> connectedIndices) {
this.connectedIndices = connectedIndices;
}
public void setIndex(int index) {
this.index = index;
}
}
GraphTest.java
#Test
public void testJsonToGraph() {
String json = "{\"nodeCount\":3,\"edgeCount\":2,"
+ "\"degreesCountMap\":[{\"ONE\":2},{\"TWO\":1}],"// <--to fail
+ "\"nodes\":[{\"index\":0,\"connectedIndices\":[1]},"
+ "{\"index\":1,\"connectedIndices\":[0,2]},"
+ "{\"index\":2,\"connectedIndices\":[1]}]}";
try {
graph = gson.fromJson(json, Graph.class);
assertNotNull(graph);
} catch (Exception e) { // Intentionally capturing to diagnose
e.printStackTrace();
}
}
The problem is that the JSON you posted is not valid.
Because Map can be used to map any object to any object Gson have to make map as array with two objects.
The valid JSON for map object would looks like this:
"degreesCountMap": [
[
"ONE",
2
],
[
"TWO",
1
]
]
but since you are using enum as keys the following code is also valid:
"degreesCountMap": {
"TWO": 1,
"ONE": 2
}
Solution: edit your json to valid one. Also, I think you are missing TWO in your degree enum.
Note: Because you use enum there is just "ONE" but if you used a typical object for a key it could looks like this:
"degreesCountMap": [
[
{ "degree": "ONE" },
2
],
[
{ "degree": "TWO" },
1
]
]

How do I iterate over a JSON response using Jackson API (of a List inside a List)?

How do I iterate over a JSON response in Java using Jackson API? In other words, if the response has a list and inside that list is another list ( in this case called 'weather') , then how do I get the temperature?
Here is an example of what I am trying to iterate through:
{
"message":"like",
"cod":"200",
"count":3,
"list":[
{
"id":2950159,
"name":"Berlin",
"coord":{
"lon":13.41053,
"lat":52.524368
},
"weather":[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"temp":74
}
]
},
{
"id":2855598,
"name":"Berlin Pankow",
"coord":{
"lon":13.40186,
"lat":52.56926
},
"weather":[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"temp":64
}
]
}
]
}
And here is the code I am trying to use, which doesn't work, because I can only iterate through the first item:
try {
JsonFactory jfactory = new JsonFactory();
JsonParser jParser = jfactory.createJsonParser( new File("test.json") );
// loop until token equal to "}"
while ( jParser.nextToken() != JsonToken.END_OBJECT ) {
String fieldname = jParser.getCurrentName();
if ( "list".equals( fieldname ) ) { // current token is a list starting with "[", move next
jParser.nextToken();
while ( jParser.nextToken() != JsonToken.END_ARRAY ) {
String subfieldname = jParser.getCurrentName();
System.out.println("- " + subfieldname + " -");
if ( "name".equals( subfieldname ) ) {
jParser.nextToken();
System.out.println( "City: " + jParser.getText() ); }
}
}
}
jParser.close();
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("-----------------");
You are parsing the JSON when Jackson is meant to do it for you. Don't do this to yourself.
One option is to create a DTO (Data Transfer Object) that matches the format of your JSON
class Root {
private String message;
private String cod;
private int count;
private List<City> list;
// appropriately named getters and setters
}
class City {
private long id;
private String name;
private Coordinates coord;
private List<Weather> weather;
// appropriately named getters and setters
}
class Coordinates {
private double lon;
private double lat;
// appropriately named getters and setters
}
class Weather {
private int id;
private String main;
private String description;
private int temp;
// appropriately named getters and setters
}
Then use an ObjectMapper and deserialize the root of the JSON.
ObjectMapper mapper = new ObjectMapper();
Root root = mapper.readValue(yourFileInputStream, Root.class);
You can then get the field you want. For example
System.out.println(root.getList().get(0).getWeather().get(0).getTemp());
prints
74
The alternative is to read your JSON in as a JsonNode and traverse it until you get the JSON element you want. For example
JsonNode node = mapper.readTree(new File("text.json"));
System.out.println(node.get("list").get(0).get("weather").get(0).get("temp").asText());
also prints
74
Based on the answer that Sotirios Delimanolis gave me, here was my solution:
ObjectMapper mapper = new ObjectMapper();
JsonFactory jfactory = mapper.getFactory();
JsonParser jParser;
try {
jParser = jfactory.createParser( tFile );
JsonNode node = mapper.readTree( jParser);
int count = node.get("count").asInt();
for ( int i = 0; i < count; i++ ) {
System.out.print( "City: " + node.get("list").get(i).get("name").asText() );
System.out.println( " , Absolute temperature: " +
node.get("list").get(i).get("main").get("temp").asText() );
}
jParser.close();
} catch (IOException e) {
e.printStackTrace();
}
I know it's old. This is my solution if you need to convert a JSON into a list and you don't have direct setters in your object.
Let's say that you have this JSON structure of 'Players':
JSON:
{
"Players":
[
{
"uid": 1, "name": "Mike",
"stats": {"shots" : 10, "hits": 5}
},
{
"uid": 2, "name": "John",
"stats": {"shots": 4, "hits": 1}
}
]
}
getListOfPlayersFromJson:
public static List<Player> getListOfPlayersFromJson(String json) {
List<Player> players = new ArrayList<>();
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
root.at("/Players").forEach(node -> {
Player p = getPlayerFromNode(node);
players.add(p);
});
} catch (IOException e) {
e.printStackTrace();
}
return players;
}
getPlayerFromNode:
public static Player getPlayerFromNode(JsonNode node) {
Player player = new Player();
player.setUid(node.at("/uid").longValue());
player.setName(node.at("/name").asText());
player.setStats(
node.at("/stats/shots").asInt(),
node.at("/stats/hits").asInt()
);
return player;
}