Start Parsing within Nested JSON Object with Jackson - java

I have the following JSON structure:
{
"uri": {
"{{firstname}}": "Peter",
"{{lastname}}": "Griffin",
"{{age}}": 42
}
}
I want to deserialize it into my Bean:
public class Uri {
private String firstname;
private String lastname;
private int age;
/* getter and setter */
}
But I get the following error:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "uri" (class com.abc.Uri), not marked as ignorable (3 known properties: "firstname", "lastname", "age")
So I assume, I need to get into the property uri.
Is there any way, to start parsing directly within the uri property?
Update:
This is how I read the JSON:
ObjectMapper mapper = new ObjectMapper();
uri = mapper.readValue(new URL("test2.json"), Uri.class);

Your method will not work because you are trying to get the whole json object at once, without getting a specific node at first.
Instead of loading your json with the mapper constructor, get your json in a different way. I would use URL and HTTPURLConnection to get the json string from the web.
After you have your json string, use this:
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(json);
Get the json node that uri represents, like this:
JsonNode uriNode = rootNode.get("uri");
And only then send that node to be parsed, like this:
Jackson 2.4
Uri uri = objectMapper.treeToValue(uriNode, Uri.class);
Prior to Jackson 2.4
Uri uri = objectMapper.readValue(uriNode, Uri.class);

You JSON should be of the format:
{
"uri": {
"firstname": "Peter",
"lastname": "Griffin",
"age": 42,
}
}

Related

How to read this json with GSON

I am trying to consume a json through an api using Google's GSON library to be able to recover the data.
I would like to be able to obtain the data of the CssValidation and Result key but it took hours and I have not been able to get it.
{
"cssvalidation": {
"uri": "https://www.mywebsite.com",
"checkedby": "http://www.w3.org/2005/07/css-validator",
"csslevel": "css3",
"date": "2021-10-17T05:07:38Z",
"timestamp": "1634490458519",
"validity": false,
"result": {
"errorcount": 7,
"warningcount": 350
}
}
}
My code in java is this:
Gson gson = new Gson();
ApiResponse apiResponse = gson.fromJson(response.body().string(), ApiResponse.class);
public class ApiResponse {
private CssValidation validation;
}
public class CssValidation{
public String uri;
public String getUri() {
return uri;
}
}
There are some problems with your json string which you need to fix before attempting to parse it, the uri value quotation is not closed, and there is an additional comma after the result object, after fixing both you end up with the functional json text
{
"cssvalidation": {
"uri": "https://www.mywebsite.com",
"checkedby": "http://www.w3.org/2005/07/css-validator",
"csslevel": "css3",
"date": "2021-10-17T05:07:38Z",
"timestamp": "1634490458519",
"validity": false,
"result": {
"errorcount": 7,
"warningcount": 350
}
}
}
you can parse it with the JsonParser and get its elements in the following way
JsonObject root = JsonParser.parseString(resp).getAsJsonObject();
JsonObject cssValidation = root.get("cssvalidation").getAsJsonObject();
String uri = cssValidation.get("uri").getAsString();
System.out.println(uri);
And you will get the following output
https://www.mywebsite.com
You seem to be doing it right, but your Java object property keys need to match up exactly with the keys in the JSON.
Specifically, the name of the property validation needs to changed to cssvalidation, so like this:
Gson gson = new Gson();
ApiResponse apiResponse = gson.fromJson(response.body().string(), ApiResponse.class);
public class ApiResponse {
private CssValidation cssvalidation;
}
public class CssValidation{
public String uri;
public String getUri() {
return uri;
}
}
Also, if the given JSON string is really what you get, then your JSON needs to be fixed, as pointed out by Lukas Ownen's answer

apply gson UPPER_CAMEL_CASE for specific sub-json

I have these messages arriving from SQS:
{
"eventID": "zzz",
"eventName": "MODIFY",
"eventVersion": "1.1",
"eventSource": "aws:dynamodb",
"awsRegion": "us-east-1",
"dynamodb": {
"ApproximateCreationDateTime": 1521976320,
"Keys": {
"key_1": {
"S": "yyy"
},
"key_2": {
"S": "xxx"
}
},
"SequenceNumber": "123",
"SizeBytes": 321,
"StreamViewType": "KEYS_ONLY"
},
"eventSourceARN": "arn:aws:dynamodb:us-east-1:eventSourceARN",
"itemType": "myItem"
}
I want to use gson library to convert this json string into a Record object (com.amazonaws.services.dynamodbv2.model.Record) which contains a StreamRecord object (com.amazonaws.services.dynamodbv2.model.StreamRecord) that represents the dynamodb sub json.
problem is that the inner fields of the dynamodb object are PascalCase while the other fields are normal camelCase.
This code:
Gson gson = new GsonBuilder()
//.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.create();
String json = <the json from the example above>
Record record = gson.fromJson(json, Record.class);
log.info("record="+record.toString());
StreamRecord dynamodb = record.getDynamodb();
log.info("dynamodb="+dynamodb.toString());
Map<String, AttributeValue> keys = dynamodb.getKeys();
log.info("keys="+keys.toString());
prints this log (UPPER_CAMEL_CASE commented out) :
record={EventID: zzz,EventName: MODIFY,EventVersion: 1.1,EventSource: aws:dynamodb,AwsRegion: us-east-1,Dynamodb: {},}
and then throws Null Pointer exception because the dynamoDb object is empty - because my json string is UPPER_CAMEL_CASE, while in the object its normal camelCase.
I want to apply FieldNamingPolicy.UPPER_CAMEL_CASE only for the dynamodb sub json.
perhaps somehow using FieldNamingStrategy ?
The json is given and I cannot change its schema.
I also can't change the fact that I get it as string.
see AWS API:
https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_Record.html
https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_StreamRecord.html
You seem to want the following naming strategy:
private static final Gson gson = new GsonBuilder()
.setFieldNamingStrategy(field -> {
if ( field.getDeclaringClass() == StreamRecord.class ) {
return FieldNamingPolicy.UPPER_CAMEL_CASE.translateName(field);
}
return FieldNamingPolicy.IDENTITY.translateName(field);
})
.create();
I usually never use naming strategies in favor of the #SerializedName annotation though, just to be more precise when declaring mappings.

Java map object, which contains JSON string fields to string

I have faced problem, while mapping my object to JSON.
I have an object, which I need to convert to propper JSON, but some of my object's String fields are already in JSON format:
Sdr sdr = new Sdr();
sdr.setLocation_area(("location_area"));
sdr.setEvent_info(("{\"chargeableDur\":0}"));
sdr.setAgent_info("{\"scp\":\"NAVI\",\"stack\":\"CAP2\"}");
sdr.setService_info(("{\"bcap\":\"8090A3\",\"balID\":55969859}"));
sdr.setStarttime(("starttime"));
For JSON mapping I am using ObjectMapper:
public String toJsonString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
logger.error(e.getMessage());
}
return toString();
}
However, ObjectMapper fails to map Strings, that already contains JSON correctly, and after mapping I get this type of JSON:
{
"event_info":""{\"chargeableDur\":0}",
"location_area":"location_area",
"agent_info":"{\"scp\":\"NAVI\",\"stack\":\"CAP2\"}",
"service_info":""{\"bcap\":\"8090A3\",\"balID\":55969859}",
"starttime":"starttime"
}
I want ObjectMapper to map my object like that:
{
"event_info":{
"chargeableDur":0
},
"location_area":"location_area",
"agent_info":{
"scp":"NAVI",
"stack":"CAP2"
},
"service_info":{
"bcap":"8090A3",
"balID":55969859
},
"starttime":"starttime"
}
Seems that your json result is stringified. Try to put the string result in separate JSONObject as
return new JSONObject(mapper.writeValueAsString(this)).toString();

Unable to parse object being returned as either START_OBJECT or START_ARRAY in the response

In one of the APIs of Expedia Affiliate Network, below part of the response is being returned as array for a few items whereas object for others,
"Surcharges": {
"Surcharge": {
"_type": "TaxAndServiceFee",
"_amount": "11.94"
},
"_size": "1"
}
"Surcharges": {
"Surcharge": [
{
"_type": "TaxAndServiceFee",
"_amount": "11.94"
},
{
"_type": "Somethingelse",
"_amount": "11.94"
}
],
"_size": "1"
}
The corresponding JAXB model is
#JsonPropertyOrder({
"#size",
"Surcharge"
})
#JsonIgnoreProperties(ignoreUnknown = true)
public class Surcharges {
#JsonProperty("#size")
private String Size;
#JsonProperty("Surcharge")
private Surcharge[] Surcharge;
//was not part of the original model, added hoping this might solve :(
#JsonProperty("Surcharge")
private Surcharge Surcharge1;
.....
}
The parse fails with
com.sun.jersey.api.client.ClientHandlerException: org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of in.xxx.xxxxx.port.adapter.expedia.hotels.list.response.Surcharge out of START_ARRAY token
and changing the Surcharge to array will result in
com.sun.jersey.api.client.ClientHandlerException: org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of in.xxx.xxxxxx.port.adapter.expedia.hotels.list.response.Surcharge out of START_OBJECT token
What is the best way to handle this response
I could find the solution in the Jackson Feature configuration
ACCEPT_SINGLE_VALUE_AS_ARRAY, how to set that with the ClientConfig of Jersey?
Resolved configuring JacksonJaxbProvider with the Client
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
JacksonJaxbJsonProvider jacksonProvider = new JacksonJaxbJsonProvider();
jacksonProvider.setMapper(mapper);
config.getSingletons().add(jacksonProvider);

RestEasy : org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token(..)

I have a rest endpoint which returns List<VariablePresentation>. I am trying to test this rest endpoint as
#Test
public void testGetAllVariablesWithoutQueryParamPass() throws Exception {
final ClientRequest clientCreateRequest = new ClientRequest("http://localhost:9090/variables");
final MultivaluedMap<String, String> formParameters = clientCreateRequest.getFormParameters();
final String name = "testGetAllVariablesWithoutQueryParamPass";
formParameters.putSingle("name", name);
formParameters.putSingle("type", "String");
formParameters.putSingle("units", "units");
formParameters.putSingle("description", "description");
formParameters.putSingle("core", "true");
final GenericType<List<VariablePresentation>> typeToken = new GenericType<List<VariablePresentation>>() {
};
final ClientResponse<List<VariablePresentation>> clientCreateResponse = clientCreateRequest.post(typeToken);
assertEquals(201, clientCreateResponse.getStatus());
final List<VariablePresentation> variables = clientCreateResponse.getEntity();
assertNotNull(variables);
assertEquals(1, variables.size());
}
This test fails with error saying
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token(..)
How can I fix this issue?
That looks like a Jackson error, where it's expecting to parse an array (which would begin with a '[') but it's encountering the beginning token for an object ('{'). From looking at your code, Im guessing it's trying deserialise JSON into your List but it's getting the JSON for an object.
What does the JSON your REST endpoint returns look like? It ought to look like this
[
{
// JSON for VariablePresentation value 0
"field0": <some-value>
<etc...>
},
<etc...>
]

Categories