I'm getting a json array holding objects looking like this:
[
{
"id": "1",
"name": "some name",
"url": "some url",
"active": true
}, {
"id": "2",
"name": "some other name",
"url": "some other url",
"active": true
}
]
Now, I want to be able to deserialize that array into a java object holding a list of the objects in the array. I have made a custom deserializer looking like this:
public class ListSerializer extends JsonDeserializer<List<Provider>>{
private static final long serialVersionUID = 9114152571639338391L;
#Override
public List<Provider> deserialize(JsonParser jsonParser,
DeserializationContext arg1) throws IOException, JsonProcessingException {
// TODO Auto-generated method stub
final ObjectCodec objectCodec = jsonParser.getCodec();
final JsonNode listOrObjectNode = objectCodec.readTree(jsonParser);
final List<Provider> result = new ArrayList<Provider>();
for (JsonNode node : listOrObjectNode) {
result.add(objectCodec.treeToValue(node, Provider.class));
}
return result;
}
}
And the class holding the list looks like this:
public class ProviderList {
#JsonDeserialize(using = ListSerializer.class)
private List<Provider> providerList;
public List<Provider> getProviderList() {
return providerList;
}
public void setProviderList(final List<Provider> providerList) {
this.providerList = providerList;
}
}
I am obviously doing something wrong, because I'm getting this error:
Can not deserialize instance of
com.wirelesscar.trailser.v1_0.domain.ProviderList out of START_ARRAY
token at [Source:
[{"id":"1","name":"Posttrack","url":"http:\dev.posttrack.com","active":true},{"id":"2","name":"Trackunit","url":"http:\dev.trackunit.com","active":true}];
line: 1, column: 1]
How can I do this properly?
You can deserialize directly to a list by using the TypeReference wrapper.
#Data
public class Provider {
private Long id;
private String name;
private String url;
private boolean active;
}
#Data
public class ProviderList {
List<Provider> providerList;
}
public class JsonTest {
#Test
public void test() {
String json = "[{\n" +
" \"id\": \"1\",\n" +
" \"name\": \"some name\",\n" +
" \"url\": \"some url\",\n" +
" \"active\": true\n" +
" }, {\n" +
" \"id\": \"2\",\n" +
" \"name\": \"some other name\",\n" +
" \"url\": \"some other url\",\n" +
" \"active\": true\n" +
" }\n" +
"]";
ObjectMapper mapper = new ObjectMapper();
try {
List<Provider> providerList = mapper.readValue(json, new TypeReference<List<Provider>>(){});
for (Provider provider : providerList) {
System.out.println(provider);
}
ProviderList list = new ProviderList();
list.setProviderList(providerList);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Related
Request:
{
"name":"iswarya",
"dept":{
"deptName":"eee",
"location":"firstfloor"
},
"additionalDetails":{
"projectName":"finalyearproject"
}
}
Response:
{
"name": "iswarya",
"deptName": null,
"location": null,
"projectName": null
}
Controller class:
#PostMapping(value="/objectMApper")
public String createEmployee(#RequestBody AnnotationTestBean demoEntity) throws JsonProcessingException {
ObjectMapper obj=new ObjectMapper();
return obj.writeValueAsString(demoEntity);
}
In the given example the request for JSON is not wrapped, so its dept and additionalDetails should not be annotated with #JsonUnwrapped.
Instead, a response should be created extending the request class, having a copy constructor, and overriding appropriate getters annotated as #JsonUnwrapped.
The example below uses Lombok annotations to generate getters/setters/constructors.
#Data
#AllArgsConstructor
#NoArgsConstructor
static class Request {
private String name;
private Department dept;
private Details additionalDetails;
}
#Data
static class Department {
private String deptName;
private String location;
}
#Data
static class Details {
private String projectName;
}
static class Response extends Request {
public Response(Request request) {
super(request.name, request.dept, request.additionalDetails);
}
#Override #JsonUnwrapped
public Department getDept() { return super.getDept(); }
#Override #JsonUnwrapped
public Details getAdditionalDetails() { return super.getAdditionalDetails(); }
}
Test
ObjectMapper om = new ObjectMapper();
String json = "{\r\n" +
" \"name\":\"iswarya\",\r\n" +
" \"dept\":{\r\n" +
" \"deptName\":\"eee\",\r\n" +
" \"location\":\"firstfloor\"\r\n" +
" },\r\n" +
" \"additionalDetails\":{\r\n" +
" \"projectName\":\"finalyearproject\"\r\n" +
" }\r\n" +
"}";
Request request = om.readValue(json, Request.class);
Response response = new Response(request);
String str = om.writerWithDefaultPrettyPrinter().writeValueAsString(response);
System.out.println(str);
Output
{
"name" : "iswarya",
"deptName" : "eee",
"location" : "firstfloor",
"projectName" : "finalyearproject"
}
Problem: Im struggling to map the child objects (items) to the Product.java class because of the extra wrapper.
Json:
{
"id": "1",
"desc": "Shopping",
"items": [
{
"product": {
"id": 2,
"name": "Sugar"
}
},
{
"product": {
"id": 1,
"name": "Flour"
}
}
]
}
Domain Model - Order(JSON Parent):
public class Order {
private int id;
private String desc;
private Set<Product> items;
}
Domain Model - Product(JSON Child)
public class Product {
private int id;
private String name;
}
How do i use jackson or any other json dependency to map this json string to these domain models ?
String data = "{" +
"\"id\": \"1\"," +
"\"desc\": \"Shopping\"," +
"\"items\": [" +
" {" +
" \"product\": {" +
" \"id\": 2," +
" \"name\": \"Sugar\"" +
" }" +
" }," +
" {" +
" \"product\": {" +
" \"id\": 1," +
" \"name\": \"Flour\"" +
" }" +
" }" +
"]" +
"}";
Gson gson = new Gson();
Order o = gson.fromJson(data, Order.class);
System.out.println(o);
The line does the trick is
Order o = gson.fromJson(data, Order.class);
Here is another solution that I managed to find using Jackson's dependency for those who arent using Gson.
In Order.java class:
public class Order {
private int id;
private String desc;
private Set<Product> items;
private static final ObjectMapper objectMapper = new ObjectMapper();
#JsonProperty("items")
public void setItems(List<Map<String, Object>> items) {
for(Map<String, Object> map: items) {
Product product = objectMapper.convertValue(map.get("product"), Product.class);
this.addItem(product);
}
}
}
I've got a following JSON from API:
"hotel_data": {
"name": "Hotel Name",
"checkin_checkout_times": {
"checkin_from": "14:00",
"checkin_to": "00:00",
"checkout_from": "",
"checkout_to": "12:00"
},
"default_language": "en",
"country": "us",
"currency": "USD",
"city": "Miami"
}
I'm using Jackson library to deserialize this JSON to Java object. I don't want to create a special class for checkin_checkout_times object. I just want to get it as a plain text. Like this "checkin_from": "14:00", "checkin_to": "00:00", "checkout_from": "", "checkout_to": "12:00".
In my POJO for hotel_data this checkin_checkout_times should be as a string i.e.:
#JsonProperty("checkin_checkout_times")
private String checkinCheckoutTimes
Is this possible to get this part of the JSON as a plain text?
EDIT: Error that I'm getting com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token
at [Source: (String)...
Make use of JsonNode.
Just make the following setter for the field checkinCheckoutTimes in your POJO for hotel_data and it should work for you.
public void setCheckinCheckoutTimes(JsonNode node) {
this.checkinCheckoutTimes = node.toString();
}
Example
String str = "{ \"id\": 1, \"data\": { \"a\": 1 } }";
try {
System.out.println(new ObjectMapper().readValue(str,Employee.class));
} catch (IOException e) {
e.printStackTrace();
}
Where Employee is as follows:
class Employee
{
private int id;
private String data;
public Employee() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(JsonNode node) {
this.data = node.toString();
}
#Override
public String toString() {
return "Employee{" +
"id=" + id +
", data='" + data + '\'' +
'}';
}
}
gives the following output:
Employee{id=1, data='{"a":1}'}
You can also write a custom deserializer as described in the article:
public class RawJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode node = mapper.readTree(jp);
return mapper.writeValueAsString(node);
}
}
and then use it with annotation in your class:
public class HotelData {
#JsonProperty("checkin_checkout_times")
#JsonDeserialize(using = RawJsonDeserializer.class)
private String checkinCheckoutTimes;
// other attributes
// getters and setters
}
I want to generate JSON schema where "additionalProperties" : false will be applied for all classes which I have.
Suppose I have following classes:
class A{
private String s;
private B b;
public String getS() {
return s;
}
public B getB() {
return b;
}
}
class B{
private BigDecimal bd;
public BigDecimal getBd() {
return bd;
}
}
When I am generating schema as following like below code the schema property "additionalProperties" : false was applying only for the class A.
ObjectMapper mapper = new ObjectMapper();
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper);
ObjectSchema schema = schemaGen.generateSchema(A.class).asObjectSchema();
schema.rejectAdditionalProperties();
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema);
How can I generate the schema where "additionalProperties" : false will be applied on all classes?
Example of schema
{
"type" : "object",
"id" : "urn:jsonschema:com.xxx.xxx:A",
"additionalProperties" : false,
"properties" : {
"s" : {
"type" : "string"
},
"b" : {
"type" : "object",
"id" : "urn:jsonschema:com.xxx.xxx:B",
"properties" : {
"bd" : {
"type" : "number"
}
}
}
}
}
Note: I don't want to generate schemes part by part.
For info:
I have opened issue for this scenario if someone interested you can support fix of this issue. Generate json schema which should rejects all additional content
You will need to specify the schema for each properties like:
ObjectMapper mapper = new ObjectMapper();
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper);
ObjectSchema schemaB = schemaGen.generateSchema(B.class).asObjectSchema();
schemaB.rejectAdditionalProperties();
ObjectSchema schema = schemaGen.generateSchema(A.class).asObjectSchema();
schema.rejectAdditionalProperties();
schema.putProperty("b", schemaB);
You can leverage reflection api to automatically do it for you. Here is a quick and dirty example:
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
final JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper);
ObjectSchema schema = generateSchema(schemaGen, A.class);
schema.rejectAdditionalProperties();
System.out.print(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema));
}
public static <T> ObjectSchema generateSchema(JsonSchemaGenerator generator, Class<T> type) throws JsonMappingException {
ObjectSchema schema = generator.generateSchema(type).asObjectSchema();
for (final Field field : type.getDeclaredFields()) {
if (!field.getType().getName().startsWith("java") && !field.getType().isPrimitive()) {
final ObjectSchema fieldSchema = generateSchema(generator, field.getType());
fieldSchema.rejectAdditionalProperties();
schema.putProperty(field.getName(), fieldSchema);
}
}
return schema;
}
Well I would go to a simpler route if you don't want to use reflections. I would use JSONPath. So you would need to add below to your pom.xml
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.3.0</version>
</dependency>
Then below code demonstrates how to alter the generated JSON file
package taruntest;
import com.jayway.jsonpath.*;
public class Test {
public static void main(String[] args) throws Exception {
String data = "{\n" +
" \"type\" : \"object\",\n" +
" \"id\" : \"urn:jsonschema:com.xxx.xxx:A\",\n" +
" \"additionalProperties\" : false,\n" +
" \"properties\" : {\n" +
" \"s\" : {\n" +
" \"type\" : \"string\"\n" +
" },\n" +
" \"b\" : {\n" +
" \"type\" : \"object\",\n" +
" \"id\" : \"urn:jsonschema:com.xxx.xxx:B\",\n" +
" \"properties\" : {\n" +
" \"bd\" : {\n" +
" \"type\" : \"number\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
DocumentContext doc = JsonPath.parse(data);
doc.put("$..[?(#.id =~ /urn:jsonschema:.*/)]", "additionalProperties", false);
String modified = doc.jsonString();
System.out.println(modified);
}
}
The output of the run is (formatted manually)
{
"type": "object",
"id": "urn:jsonschema:com.xxx.xxx:A",
"additionalProperties": false,
"properties": {
"s": {
"type": "string"
},
"b": {
"type": "object",
"id": "urn:jsonschema:com.xxx.xxx:B",
"properties": {
"bd": {
"type": "number"
}
},
"additionalProperties": false
}
}
}
The following worked for me:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kjetland.jackson.jsonSchema.JsonSchemaConfig;
import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator;
...
ObjectMapper objectMapper = new ObjectMapper();
JsonSchemaConfig config = JsonSchemaConfig.nullableJsonSchemaDraft4();
JsonSchemaGenerator schemaGenerator = new JsonSchemaGenerator(objectMapper, config);
JsonNode jsonNode = schemaGenerator.generateJsonSchema(Test.class);
String jsonSchemaText = jsonNode.toString();
Using maven dependency:
<dependency>
<groupId>com.kjetland</groupId>
<artifactId>mbknor-jackson-jsonschema_2.12</artifactId>
<version>1.0.28</version>
</dependency>
Using the following classes:
Test.java:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Test {
#JsonProperty(required = true)
private final String name;
private final TestChild child;
#JsonCreator
public Test (
#JsonProperty("name") String name,
#JsonProperty("child") TestChild child) {
this.name = name;
this.child = child;
}
public String getName () {
return name;
}
public TestChild getChild () {
return child;
}
}
...and TestChild.java:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TestChild {
#JsonProperty(required = true)
private final String childName;
#JsonCreator
public TestChild (#JsonProperty("childName") String childName) {
this.childName = childName;
}
public String getChildName () {
return childName;
}
}
Results in (output of jsonSchemaText piped through jq -C . for pretty formatting):
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Test",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"child": {
"oneOf": [
{
"type": "null",
"title": "Not included"
},
{
"$ref": "#/definitions/TestChild"
}
]
}
},
"required": [
"name"
],
"definitions": {
"TestChild": {
"type": "object",
"additionalProperties": false,
"properties": {
"childName": {
"type": "string"
}
},
"required": [
"childName"
]
}
}
}
This results in "additionalProperties": false on both Test and TestChild.
Note: You can replace JsonSchemaConfig.nullableJsonSchemaDraft4() with JsonSchemaConfig.vanillaJsonSchemaDraft4() in your schema generation code to get rid of the "oneof" references with "type: null" or "type: ActualType" in favor of just "type: ActualType" (note, this still won't add them to the "required" array unless you annotate the properties with #JsonProperty(required = true)).
This is my solution, without any reflect and hack way, and it works very well for me.
public static void rejectAdditionalProperties(JsonSchema jsonSchema) {
if (jsonSchema.isObjectSchema()) {
ObjectSchema objectSchema = jsonSchema.asObjectSchema();
ObjectSchema.AdditionalProperties additionalProperties = objectSchema.getAdditionalProperties();
if (additionalProperties instanceof ObjectSchema.SchemaAdditionalProperties) {
rejectAdditionalProperties(((ObjectSchema.SchemaAdditionalProperties) additionalProperties).getJsonSchema());
} else {
for (JsonSchema property : objectSchema.getProperties().values()) {
rejectAdditionalProperties(property);
}
objectSchema.rejectAdditionalProperties();
}
} else if (jsonSchema.isArraySchema()) {
ArraySchema.Items items = jsonSchema.asArraySchema().getItems();
if (items.isSingleItems()) {
rejectAdditionalProperties(items.asSingleItems().getSchema());
} else if (items.isArrayItems()) {
for (JsonSchema schema : items.asArrayItems().getJsonSchemas()) {
rejectAdditionalProperties(schema);
}
}
}
}
I cannot create Java Getters and Setters, because I got number(digit) for my Object Key.
I will show you my API response. How can I achieve this without changing the API.
{"api_status": true,
"message": "",
"data": {
"0": {
"id": "aaa",
"name": "aaa",
"address": "aaa",
"category": "aaa",
"open_24_hours": "aaa",
"business_open": "",
"business_close": "",
"type": "0",
"title": null,
"latitude": "6.8729428",
"longitude": "79.8689013",
"city": "",
"distance": "2.95555089735992"
},
"1": {
"id": "bbb",
"name": "bbb",
"address": "bbb",
"category": "bbb",
"open_24_hours": "bbb",
"business_open": "",
"business_close": "",
"type": "0",
"title": null,
"latitude": "6.8767581",
"longitude": "79.8674747",
"city": "",
"distance": "2.915385898910569"
},
}
}
Use the below class and pass it to GSON library with your json data and the Class As a model . you will get your model, each data item is mapped with hashtable where key is your number which i represent as string By iterating over hash map you will get keySet which is your all keys in the data key of json. and for each key you can get itemData.
class JsonStructure{
public boolean api_status;
public String message
HashMap<String,ItemsData> data;
}
class ItemsData{
public String id;
public String name;
public String address;
public String category;
public String open_24_hours;
public String business_open;
public String business_close;
public String type;
public String title;
public String latitude;
public String longitude;
public String city;
public String distance;
}
For retrofit Build
BuildRetrofit(){
mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
mConverterFactory = GsonConverterFactory.create();
String baseUrl = "http://dev.appslanka.com/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(mOkHttpClient)
.addConverterFactory(mConverterFactory)
.build();
mApi = retrofit.create(ApiInterface.class);
}
In ApiInterface define yoyr request method
interface ApiInterface{
#GET("_test/placeInDistance/")
Call<JsonStructure> getResponseForApiCall();
}
Now call this method as retrofit call structure:
Call<JsonStructure> call = mApi.getResponseForApiCall();
Response<JsonStructure> response = call.execute();
Parse this response like below:
HashMap<String, ItemsData> map = response .data;
Set<String> s = map.keySet();
Iterator<String> i = s.iterator();
while (i.hasNext()){
String key = i.next();
ItemsData data = map.get(key);
String id = data.id;
String name = data.name;
String address = data.address;
String category = data.category;
String open24Hr = data.open_24_hours;
String businessOpen = data.business_open;
String close = data.business_close;
String latitue = data.latitude;
..... etc
}
Yes, you can. Use SerializedName annotation like this:
#SerializedName("0")
private MyClass myObject;
Where MyClass is gonna represent a POJO for the data you're getting back.
I just want to note that a better solution would be to change the API (cause this response is weird), to return a list rather than an object with digits for keys, but I can see that you wrote in the question that you cannot change it.
If you really need to parse this JSON. Use custom solution.
For example my solution.
Create class Response with following code :
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class Response {
public boolean apiStatus;
public String message;
public List<Data> datas;
public Response(JSONObject jsonObject) {
apiStatus = jsonObject.optBoolean("api_status");
message = jsonObject.optString("message");
datas = new ArrayList<>();
try {
JSONObject datasJSON = jsonObject.getJSONObject("data");
int index = 0;
while (datasJSON.has(String.valueOf(index))) {
JSONObject dataJSON = datasJSON.getJSONObject(String.valueOf(index));
datas.add(new Data(dataJSON));
index++;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override public String toString() {
return "Response{" +
"apiStatus=" + apiStatus +
", message='" + message + '\'' +
", datas=" + datas +
'}';
}
}
Create class Data with following code :
import org.json.JSONObject;
public class Data {
public String id;
public String name;
public String address;
public String category;
public String open24Hours;
public String businessOpen;
public String businessClose;
public String type;
public String title;
public String latitude;
public String longitude;
public String city;
public String distance;
public Data(JSONObject jsonObject) {
id = jsonObject.optString("id");
name = jsonObject.optString("name");
address = jsonObject.optString("address");
category = jsonObject.optString("category");
open24Hours = jsonObject.optString("open_24_hours");
businessOpen = jsonObject.optString("business_open");
businessClose = jsonObject.optString("business_close");
type = jsonObject.optString("type");
title = jsonObject.optString("title");
latitude = jsonObject.optString("latitude");
longitude = jsonObject.optString("longitude");
city = jsonObject.optString("city");
distance = jsonObject.optString("distance");
}
#Override public String toString() {
return "Data{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", address='" + address + '\'' +
", category='" + category + '\'' +
", open24Hours='" + open24Hours + '\'' +
", businessOpen='" + businessOpen + '\'' +
", businessClose='" + businessClose + '\'' +
", type='" + type + '\'' +
", title='" + title + '\'' +
", latitude='" + latitude + '\'' +
", longitude='" + longitude + '\'' +
", city='" + city + '\'' +
", distance='" + distance + '\'' +
'}';
}
}
Instruction for use this solution:
Response response = new Response(jsonObject);
Instruction for use it, when you use Retrofit2.
For first we need to create custom factory, create class with name ResponseRetrofitConverter, and this following code :
import android.support.annotation.NonNull;
import org.json.JSONObject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
public class ResponseRetrofitConverter extends Converter.Factory {
public static ResponseRetrofitConverter create() {
return new ResponseRetrofitConverter();
}
#Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return new JsonConverter();
}
private final static class JsonConverter implements Converter<ResponseBody, Response> {
#Override
public Response convert(#NonNull ResponseBody responseBody) {
try {
return new Response(new JSONObject(responseBody.string()));
} catch (Exception e) {
return null;
}
}
}
}
When Response is your entity,
Add connect with factory to retrofit use following code line :
.addConverterFactory(ResponseRetrofitConverter.create())
For example my code:
Retrofit.Builder()
.baseUrl(link)
.addConverterFactory(ResponseRetrofitConverter.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
You should create a java List of objects to represent the data.
If you want to bind a Json that has a number as name, and if you are using jackson as json library, you can declare the variable as follow:
#JsonProperty("0")
private CustomObject zero;
#JsonProperty("1")
private CustomObject one;
public CustomObject getZero()
{
return this.zero;
}
public void setZero(CustomObject zero)
{
this.zero= zero;
}
public CustomObject getOne()
{
return this.one;
}
public void setOne(CustomObject one)
{
this.one= one;
}
If you are using Gson then you can use as follows:
public class Model{
#SerializedName("0")
private String object;
}
You can call you class _0, _1... even it's a little bit strange.