jackson deserialization of json with a numbered array - java

I have a JSON string like this:
{
"1": {
"entity_id":"1",
"status":"canceled"
},
"2": {
"entity_id":"2",
"status":"pending"
}
}
I am struggling to find the correct settings to use when deserializing this. I stumble on what looks like mapping the "1" and "2" id's in the above.
My object mapper looks like this:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationConfig.Feature.READ_ENUMS_USING_TO_STRING, true);
mapper.configure(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
I've tried all sorts of classes to map it to, but none of them have worked when using the following line:
XXX jsonObject = mapper.readValue(json, XXX.class);
Any suggestions on what the XXX class should look like?

The solution was to map the json into a map as follows:
Map<String, Object> map = mapper.readValue(message, new TypeReference<Map<String, Object>>() {});
In the actual code I replaced Object with a java class that maps the fields such as entity_id and status

Related

Java get nested value from ResponseEntity without creating a pojo

I am trying to get a single nested value from a ResponseEntity but I am trying to do so without having to create a pojo for every possible item as this is a third party api response.
Example response.getBody() as it appears in Postman:
{
"message": "2 records found",
"records": [
{
"Account": {
"Id": "1",
"Name": "Foo Inc"
},
"CaseNumber": "200",
"Contact": {
"FirstName": "Foo",
"LastName": "Bar"
},
"Status": "In Progress",
"StatusMessage": "We are working on this."
},
{
"Account": {
"Id": "1",
"Name": "Foo Inc"
},
"CaseNumber": "100",
"Contact": {
"FirstName": "Foo",
"LastName": "Bar"
},
"Status": "Closed"
}
]
}
Basically, if I were in JS, I am looking for:
for(let record of res.body.records){
if(record && record.CaseNumber === "200"){
console.log(record.Status)
}
res.body.records[0].Status
Currently, they are are doing this to check if the response is empty:
ResponseEntity<Object> response = restTemplate.exchange(sfdcURL, HttpMethod.POST, entity, Object.class);
LinkedHashMap<Object, Object> resMap = (LinkedHashMap<Object, Object>) response.getBody();
List<Object> recordsList = (List<Object>) resMap.get("records");
if (recordsList.size() <= 0) { return error }
But I need to get the value of of "Status" and I need to do so without creating a pojo.
I appreciate any guidance on how I can do this in Java
UPDATE
So the response.getBody() is returned and when it is displayed in Postman, it looks like the pretty JSON shown above. However, when I do:
System.out.println(response.getBody().toString())
it looks like:
{message=2 Records Found, records=[{Account={Id=1, Name=Foo Inc}, CaseNumber=200, Contact={FirstName=Foo, LastName=Bar}, //etc
To make it worse, one of the fields appears in the console as follows (including linebreaks):
[...], Status=In Progress, LastEmail=From: noreply#blah.com
Sent: 2022-08-08 10:14:54
To: foo#bar.com
Subject: apropos case #200
Hello Foo,
We are working on your case and stuff
Thank you,
us, StatusMessage=We are working on this., OtherFields=blah, [...]
text.replaceAll("=", ":") would help some, but won't add quotations marks nor would it help separate that email block.
How can I so that the responses here like ObjectMapper and JSONObject can work?
You can either convert the string to valid json (not that trivial) and deserialise into a Map<String, Object>, or just pluck the value out of the raw string using regex:
String statusOfCaseNumber200 = response.getBody().toString()
.replaceAll(".*CaseNumber=200\\b.*?\\bStatus=([^,}]*).*", "$1");
This matches the whole string, captures the desired status value then replaces with the status, effectively "extracting" it.
The regex:
.*CaseNumber=200\b everything up to and including CaseNumber=200 (not matching longer numbers like 2001)
.*? as few chars as possible
\\bStatus= "Status=" without any preceding word chars
([^,}]*) non comma/curly brace characters
.* the rest
It's not bulletproof, but it will probably work for your use case so it doesn't need to be bulletproof.
Some test code:
String body = "{message=2 Records Found, records=[{Account={Id=1, Name=Foo Inc}, CaseNumber=200, Contact={FirstName=Foo, LastName=Bar}, Status=In Progress, StatusMessage=We are working on this.}, {Account={Id=1, Name=Foo Inc}, CaseNumber=100, Contact={FirstName=Foo, LastName=Bar}, Status=Closed}]";
String statusOfCaseNumber200 = body.replaceAll(".*CaseNumber=200\\b.*?\\bStatus=([^,}]*).*", "$1");
System.out.println(statusOfCaseNumber200); // "In Progress"
PLEASE DO NOT use Genson as Hiran showed in his example. The library hasn't been updated since 2019 and has many vulnerable dependencies!
Use Jackson or Gson.
Here how you can serialize a string into a Jackson JsonNode:
ObjectMapper mapper = new ObjectMapper();
String json = ...;
JsonNode node = mapper.readTree(json);
If you want to serialize a JSON object string into a Map:
ObjectMapper mapper = new ObjectMapper();
String json = ...;
Map<String, Object> map = mapper.readValue(json, HashMap.class);
You can read more about JsonNode here and a tutorial here.
You can use JSON-Java library and your code will look like this:
JSONObject jsonObject = new JSONObject(JSON_STRING);
String status = jsonObject.getJSONArray("records")
.getJSONObject(0)
.getString("Status");
System.out.println(status);
Or in a loop
JSONArray jsonArray = new JSONObject(jsonString).getJSONArray("records");
for(int i =0; i < jsonArray.length(); i++) {
String status = jsonArray
.getJSONObject(i)
.getString("Status");
System.out.println(status);
}
So the response.getBody() is returned and when it is displayed in Postman, it looks like the pretty JSON shown above. However, when I do:
...
text.replaceAll("=", ":") would help some, but won't add quotations
marks nor would it help separate that email block.
How can I so that the responses here like ObjectMapper and JSONObject
can work?
Firstly, Jackson is the default message converter which Spring Web uses under the hood to serialize and deserialize JSON. You don't need to introduce any dependencies.
Secondly, the process serialization/deserialization is handled by the framework automatically, so that in many cases you don't need to deal with the ObjectMapper yourself.
To emphasize, I'll repeat: in most of the cases in Spring you don't need to handle raw JSON yourself. And in the body of ResponseEntiry<Object> produced by the method RestTemplate.exchange() you have a LinkedHashMap in the guise of Object, it's not a raw JSON (if you want to know why it is a LinkedHashMap, well because that's how Jackson stores information, and it's a subclass of Object like any other class in Java). And sure, when you're invoking toString() on any implementation of the Map you'll get = between a Key and a Value.
So, the problem you've mentioned in the updated question is artificial.
If you want to deal with a Map instead of an object with properly typed properties and here's how you can do that:
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<LinkedHashMap<String, Object>> response = restTemplate.exchange(
sfdcURL, HttpMethod.POST, entity, new ParameterizedTypeReference<>() {}
);
Map<String, Object> resMap = response.getBody();
List<Object> recordsList = (List<Object>) resMap.get("records");
if (recordsList.isEmpty()) { ... }
If there are redundant lines in the Values which you want to trim, then as a remedy you can introduce a custom Jackson-module declaring a Deserializer which would handle leading/trailing white-space and new lines, described in this answer. Deserialize in the module would be applied by default, other options would require creating classes representing domain objects which you for some reasons want to avoid.
As Oliver suggested JsonNode seems to be the best approach. But, if I receive the ResponseEntity<Object>, I still cannot figure out a way to convert it to readable Json (and thus convert it to JsonNode), so I am still open to responses for that part.
I was able to get it to work by changing the ResponseEntity<Object> to ResponseEntity<JsonNode> so this is what I will be submitting for now:
ResponseEntity<JsonNode> response = restTemplate.exchange(sfdcURL,
HttpMethod.POST, entity, JsonNode.class);
JsonNode records = response.getBody().get("records");
String status = null;
String statusMessage = null;
for (JsonNode rec : records) {
if(rec.get("CaseNumber").asText().equals(caseNumber)) {
status = rec.get("Status").asText();
if(rec.has("StatusMessage")) {
statusMessage = rec.get("StatusMessage").asText();
}
} else {
statusMessage = "Invalid CaseNumber";
}
}
Because the overall method returns a ResponseEntity<Object> I then converted my strings to a HashMap and returned that:
HashMap<String, String> resMap = new HashMap<String, String>();
resMap.put("Status", status);
resMap.put("StatusMessage", statusMessage);
return new ResponseEntity<>(resMap, HttpStatus.OK);
This is not a perfect solution, but it works for now. Would still be better for exception handling if I could receive a ResponseEntity<Object> and then convert it to a JsonNode though. Thanks everyone for the responses!

Why parsing Invalid json returns Empty Object [duplicate]

I need GSON mapper to throw an exception if json contains unknown fields. For example if we have POJO like
public class MyClass {
String name;
}
and json like
{
"name": "John",
"age": 30
}
I want to get some sort of message that json contains unknown field (age) that can not be deserialized.
I know there is out-of-box solution in Jackson mapper, but in our project we have been using Gson as a mapper for several years and using Jackson ends up in conflicts and bugs in different parts of project, so it is easier for me to write my own solution than using Jackson.
In other words, I want to know if there is some equivalent to Jackson's DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES in Gson. Or maybe if it can be done using Gson's DeserializationStrategy other than using reflections
I believe you cannot do it automatically with Gson.
I had to do this in a project at work. I did the following:
Gson GSON = new GsonBuilder().create();
(static final) Map<String, Field> FIELDS = Arrays.stream(MyClass.class.getDeclaredFields())
.collect(Collectors.toMap(Field::getName, Function.identity()));
JsonObject object = (JsonObject) GSON.fromJson(json, JsonObject.class);
List<String> objectProperties = object.entrySet().stream().map(Entry::getKey).collect(Collectors.toList());
List<String> classFieldNames = new ArrayList<>(FIELDS.keySet());
if (!classFieldNames.containsAll(objectProperties)) {
List<String> invalidProperties = new ArrayList<>(objectProperties);
invalidProperties.removeAll(classFieldNames);
throw new RuntimeException("Invalid fields: " + invalidProperties);
}

How to make GSON fail on unknown properties

I need GSON mapper to throw an exception if json contains unknown fields. For example if we have POJO like
public class MyClass {
String name;
}
and json like
{
"name": "John",
"age": 30
}
I want to get some sort of message that json contains unknown field (age) that can not be deserialized.
I know there is out-of-box solution in Jackson mapper, but in our project we have been using Gson as a mapper for several years and using Jackson ends up in conflicts and bugs in different parts of project, so it is easier for me to write my own solution than using Jackson.
In other words, I want to know if there is some equivalent to Jackson's DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES in Gson. Or maybe if it can be done using Gson's DeserializationStrategy other than using reflections
I believe you cannot do it automatically with Gson.
I had to do this in a project at work. I did the following:
Gson GSON = new GsonBuilder().create();
(static final) Map<String, Field> FIELDS = Arrays.stream(MyClass.class.getDeclaredFields())
.collect(Collectors.toMap(Field::getName, Function.identity()));
JsonObject object = (JsonObject) GSON.fromJson(json, JsonObject.class);
List<String> objectProperties = object.entrySet().stream().map(Entry::getKey).collect(Collectors.toList());
List<String> classFieldNames = new ArrayList<>(FIELDS.keySet());
if (!classFieldNames.containsAll(objectProperties)) {
List<String> invalidProperties = new ArrayList<>(objectProperties);
invalidProperties.removeAll(classFieldNames);
throw new RuntimeException("Invalid fields: " + invalidProperties);
}

How to map Json (from procedure) to Java object

I have the following SP (SQL server) that return a Json output.
BEGIN
SET #jsonOutput = (
SELECT
Program.Name AS ProgramName,
ProgramOwner.FirstName AS OwnerFirstName,
FROM ProgramOwner, Program
WHERE Program.Id = ProgramOwner.ProgramOwner2Program
FOR JSON PATH,WITHOUT_ARRAY_WRAPPER)
I would like to map the return Json output to a List of ProgramDto via modelMapper. Not sure hot to do that since the return values from call.execute is an Object.
Something like this:
SimpleJdbcCall call = new
SimpleJdbcCall(jdbcTemplate).withProcedureName(programProc).declareParameters(
new SqlOutParameter("jsonOutput", Types.VARCHAR));
Map<String,Object>out = call.execute(new MapSqlParameterSource());
if(out.size()>0) {
// Only to show what I am trying to do
Type rootType = new TypeToken<List<ProgramDto>>() {}.getType();
modelMapper.map(out.get("jsonOutput"),rootType );
}
Thank you
As I understood you are trying to get a list of object from
You can use Jackson api
Like this
say for example your json is in variable named jsonData, then you can get the object you need like below.
ObjectMapper mapper = new ObjectMapper();
List<Type> myList = Arrays.asList(mapper.readValue(jsonData, Type[].class));
You can also find more examples here

Convert AWS APIGateway's Model to JSON in Java

I am using Amazon's APIGateway service client side. When you make a request the data returned is stored in a Model data type that the schema is set up beforehand. the calls look like this:
MyModel myModel = client.settingsPost();
String volume = myModel.getVolume();
the schema for this simple object would look like this:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "MyModel",
"type": "object",
"properties" : {
"volume" : { "type" : "string" }
}
}
I would like to convert the Model returned directly to JSON instead of having to go manually reconstruct a new JSONObject from each value of this Model. The Models seem to be very simple and I cant even iterate through them. But I wonder if there is a way to convert them using the GSON library somehow?
EDIT: I am using the APIGateway SDK generated to Java.
Using Jackson:
ObjectMapper mapper = new ObjectMapper();
String jsonInString = mapper.writeValueAsString(myModel);
Using Gson:
Gson gson = new Gson();
String json = gson.toJson(myModel);

Categories