Related
I have a json file with which has an array of product items i want to split them based on the category of the item,
This is my json file looks like,
{
"items":[
{
"item-id": 123,
"name": "Cheese",
"price": 8,
"category": "Dairy"
},
{
"item-id": 124,
"name": "Milk",
"price": 23,
"category": "Dairy"
},
{
"item-id": 125,
"name": "Chicken",
"price": 100,
"category": "Meat"
},
{
"item-id": 126,
"name": "Fish",
"price": 45,
"category": "Meat"
}
]
}
i want to split them like like this,
[
{
"category":"Dairy",
"items":[
{
"item-id": 123,
"name": "Cheese",
"price": 8,
"category": "Dairy"
},
{
"item-id": 124,
"name": "Milk",
"price": 23,
"category": "Dairy"
}
]
},
{
"category":"Meat",
"items":[
{
"item-id": 125,
"name": "Chicken",
"price": 100,
"category": "Meat"
},
{
"item-id": 126,
"name": "Fish",
"price": 45,
"category": "Meat"
}
]
}
]
this is the code i tried so far but can't find way to split them like the way i wanted, I'm using java and also i am new to java
import java.io.*;
import java.util.*;
import org.json.simple.*;
import org.json.simple.parser.*;
public class ReadOrderDetails {
#SuppressWarnings("unused")
public static void main(String[] args) {
JSONParser parser = new JSONParser();
JSONObject subOrder = new JSONObject();
JSONArray gitems = new JSONArray();
JSONArray subOrders = new JSONArray();
try {
Object obj = parser.parse(new FileReader("order-details.json"));
JSONObject jsonObject = (JSONObject)obj;
String orderId = (String)jsonObject.get("orderId");
JSONArray items = (JSONArray)jsonObject.get("items");
#SuppressWarnings("rawtypes")
Iterator iterator = items.iterator();
System.out.println("Order Id: " + orderId);
while(iterator.hasNext()) {
JSONObject item = (JSONObject)iterator.next();
if(subOrders.isEmpty()) {
subOrder.put("category", item.get("category"));
gitems.add(item);
subOrder.put("items", gitems);
subOrders.add(subOrder);
} else {
Iterator subOrdersIterator = subOrders.iterator();
for(int i=0; i<subOrders.size(); i++) {
JSONObject sitem = (JSONObject) subOrdersIterator.next();
if(sitem.get("category") == item.get("category")) {
gitems.add(item);
subOrder.put("items", gitems);
subOrders.add(subOrder);
} else {
subOrder.put("category", item.get("category"));
gitems.add(item);
subOrder.put("items", gitems);
subOrders.add(subOrder);
}
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
System.out.println(subOrders);
}
}
and also i'm getting an error at java.util.ConcurrentModificationException but that's not my main question, what i really wnated a way to split them i tried couple of things didn't working
In general, you violated the Single Responsibility principal (SOLID), because your code do two different things together: parse a file and categorize items. You should split these responsibilities.
One of ways how you do this is to create classes that represents your data. Let's assume that these classes are Item, Category, Order and CategorizedItems. Order is a class that represents your source JSONObject and CategorizedItems - represents your result JSONArray.
In this case, you should firstly parse the file into these classes and only after that transform them into JSONArray.
Code sample:
Data classes:
class Order {
private final List<Item> items = new ArrayList<>();
public void addItem(Item item) {
items.add(item);
}
public List<Item> getAllItems() {
return new ArrayList<>(items);
}
}
enum Category {
DAIRY("Dairy"),
MEAT("Meat");
private final String value;
Category(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static Category of(String value) {
for (Category category : values()) {
if (category.value.equals(value)) {
return category;
}
}
throw new IllegalArgumentException("Unknown category");
}
}
class CategorizedItems {
private final Category category;
private final List<Item> items;
public CategorizedItems(Category category, List<Item> items) {
this.category = category;
this.items = items;
}
public Category getCategory() {
return category;
}
public List<Item> getItems() {
return items;
}
}
class Item {
private final long id;
private final String name;
private final long price;
private final Category category;
public Item(long id, String name, long price, Category category) {
this.id = id;
this.name = name;
this.price = price;
this.category = category;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public long getPrice() {
return price;
}
public Category getCategory() {
return category;
}
}
Now you should write methods, for example, static ones in your ReadOrderDetails:
private static JSONArray categorizedItemsToJSONArray(List<CategorizedItems> categorizedItems) {
JSONArray jsonArray = new JSONArray();
categorizedItems.forEach(category -> jsonArray.add(toJSONObject(category)));
return jsonArray;
}
private static JSONObject toJSONObject(CategorizedItems categorizedItems) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("category", categorizedItems.getCategory().getValue());
jsonObject.put("items", itemsToJSONArray(categorizedItems.getItems()));
return jsonObject;
}
private static JSONArray itemsToJSONArray(List<Item> items) {
JSONArray jsonArray = new JSONArray();
items.forEach(item -> jsonArray.add(toJSONObject(item)));
return jsonArray;
}
private static JSONObject toJSONObject(Item item) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("item-id", item.getId());
jsonObject.put("name", item.getName());
jsonObject.put("price", item.getName());
jsonObject.put("category", item.getCategory().getValue());
return jsonObject;
}
private static List<CategorizedItems> categorize(Order order) {
List<CategorizedItems> categorizedItems = new ArrayList<>();
for (Category category : Category.values()) {
categorizedItems.add(
new CategorizedItems(
category,
order.getAllItems()
.stream()
.filter(item -> item.getCategory() == category)
.collect(Collectors.toList())
)
);
}
return categorizedItems;
}
private static Order orderFrom(JSONObject jsonObject) {
JSONArray itemsJsonArray = (JSONArray) jsonObject.get("items");
Order order = new Order();
for (Object jsonArrayMember : itemsJsonArray) {
JSONObject itemJsonObject = (JSONObject) jsonArrayMember;
order.addItem(itemFrom(itemJsonObject));
}
return order;
}
private static Item itemFrom(JSONObject jsonObject) {
long itemId = (Long) jsonObject.get("item-id");
String itemName = (String) jsonObject.get("name");
long itemPrice = (Long) jsonObject.get("price");
Category category = Category.of((String) jsonObject.get("category"));
return new Item(
itemId,
itemName,
itemPrice,
category
);
}
After that you should do the last step. Write a main function like:
public static void main(String[] args) {
JSONParser parser = new JSONParser();
try {
JSONObject orderJsonObject = (JSONObject) parser.parse(new FileReader("order-details.json"));
Order order = orderFrom(orderJsonObject);
List<CategorizedItems> categorizedItems = categorize(order);
JSONArray categorizedItemsJsonArray = categorizedItemsToJSONArray(categorizedItems);
// write categorizedItemsJsonArray to a file here
} catch(IOException | ParseException e) {
throw new RuntimeException(e);
}
}
Now your code is simply readable and clear. You can easily maintain it. I advise you to read about SOLID principals and about code style (I highly recommend Robert Martin. "Clean code"). These topics will help you learn how to write elegant and clear code.
Besides, ConcurrentModificationException is a sign that you do something wrong with collections. In your particular case, I guess (not sure) the problem is modifying the JSONArray while you are iteratting it. Only some of Java collections allow it.
EDIT Fix problem with integers by replacing them with long.
Good luck with Java :)
You can't alter a collection while iterating it. Try this:
Object[] subOrderArray = subOrders.toArray();
for(Object o: subOrderArray) {
JSONObject sitem = (JSONObject) o;
Here you want to group the JSON data by category under items array. The code will be very long if you try to get this done in Java. Just try using SPL, an open-source Java package, to do this. You only need one line of code:
A
1
=json(file("data.json").read()).items.group(#4;~:items)
SPL offers JDBC driver to be invoked by Java. Just store the above SPL script as group.splx and invoke it in a Java application as you call a stored procedure:
…
Class.forName("com.esproc.jdbc.InternalDriver");
con= DriverManager.getConnection("jdbc:esproc:local://");
st = con.prepareCall("call group()");
st.execute();
…
Library Josson can do the job with a very simple statement.
https://github.com/octomix/josson
Deserialization
Josson josson = Josson.fromJsonString(
"{" +
" \"items\":[" +
" {" +
" \"item-id\": 123," +
" \"name\": \"Cheese\"," +
" \"price\": 8," +
" \"category\": \"Dairy\" " +
" }," +
" {" +
" \"item-id\": 124," +
" \"name\": \"Milk\"," +
" \"price\": 23," +
" \"category\": \"Dairy\"" +
" }," +
" {" +
" \"item-id\": 125," +
" \"name\": \"Chicken\"," +
" \"price\": 100," +
" \"category\": \"Meat\"" +
" }," +
" {" +
" \"item-id\": 126," +
" \"name\": \"Fish\"," +
" \"price\": 45," +
" \"category\": \"Meat\"" +
" }" +
" ]" +
"}");
Transformation
JsonNode node = josson.getNode("items.group(category, items:?)");
System.out.println(node.toPrettyString());
Output
[ {
"category" : "Dairy",
"items" : [ {
"item-id" : 123,
"name" : "Cheese",
"price" : 8,
"category" : "Dairy"
}, {
"item-id" : 124,
"name" : "Milk",
"price" : 23,
"category" : "Dairy"
} ]
}, {
"category" : "Meat",
"items" : [ {
"item-id" : 125,
"name" : "Chicken",
"price" : 100,
"category" : "Meat"
}, {
"item-id" : 126,
"name" : "Fish",
"price" : 45,
"category" : "Meat"
} ]
} ]
I write app in Spring.
This is my json: (it is an array of json objects)
[{"id" : 643419352,
"status" : "removed_by_user",
"url" : "https://www.olx.pl/d/oferta/opona-12-1-2-x-2-1-4-etrto-62-203-detka-CID767-IDHxILu.html",
"created_at" : "2020-11-27 10:46:07",
"activated_at" : "2020-12-11 12:41:12",
"valid_to" : "2020-12-17 15:38:10",
"title" : "opona 12 1/2 \" x 2 1/4 etrto 62-203 + dętka",
"description" : "opona w bardzo dobrym stanie + dętka, rozmiar 12 1/2 x 2 1/4 , dętka z zaworem samochodowym",
"category_id" : 1655,
"advertiser_type" : "private",
"external_id" : null,
"external_url" : null,
"contact" : {
"name" : "Damazy",
"phone" : "501474399"
},
"location" : {
"city_id" : 10609,
"district_id" : 301,
"latitude" : "51.80178",
"longitude" : "19.43928"
},
"images" : [ {
"url" : "https://ireland.apollo.olxcdn.com:443/v1/files/efa9any4ryrb-PL/image;s=1000x700"
} ],
"price" : {
"value" : "9",
"currency" : "PLN",
"negotiable" : false,
"budget" : false,
"trade" : false
},
"salary" : null,
"attributes" : [ {
"code" : "state",
"value" : "used",
"values" : null
} ],
"courier" : null
}, {
"id" : 643435839,
"status" : "removed_by_user",
"url" : "https://www.olx.pl/d/oferta/opona-4-80-4-00-8-do-taczki-nowa-CID628-IDHxN3p.html",
"created_at" : "2020-11-27 11:53:47",
"activated_at" : "2020-11-27 11:54:36",
"valid_to" : "2020-12-17 15:38:07",
"title" : "opona 4.80/4.00 - 8 do taczki nowa!!!",
"description" : "opona do taczki, nowa, nigdy nie używana, stan idealny.\r\nrozmiar 4.80/4.00-8. \r\nopona do taczki, nowa, nigdy nie używana, stan idealny.\r\nrozmiar 4.80/4.00-8.",
"category_id" : 1636,
"advertiser_type" : "private",
"external_id" : null,
"external_url" : null,
"contact" : {
"name" : "Damazy",
"phone" : "501474399"
},
"location" : {
"city_id" : 10609,
"district_id" : 301,
"latitude" : "51.80178",
"longitude" : "19.43928"
},
"images" : [ {
"url" : "https://ireland.apollo.olxcdn.com:443/v1/files/qmvssagjnq1r2-PL/image;s=1000x700"
} ],
"price" : {
"value" : "9",
"currency" : "PLN",
"negotiable" : false,
"budget" : false,
"trade" : false
},
"salary" : null,
"attributes" : [ {
"code" : "state",
"value" : "new",
"values" : null
} ],
"courier" : null
}]
and this is my entity class Advert :
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class Advert {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long ident;
private int id;
private String status;
private String url;
private String created_at;
private String activated_at;
private String valid_to;
private String title;
#Lob
private String description;
private int category_id;
private String advertiser_type;
private Long external_id;
private String external_url;
private String salary;
private String attributes;
private String courier;
#Embedded
private Location location;
#Embedded
private Contact contact;
#Embedded
private Price price;
private String images;
and my saveAdverts method :
#RequestMapping("/saveadverts")
public String saveAdverts() throws IOException {
HttpEntity<String> requestEntity = entity.requestEntityProvider();
String url = "https://www.olx.pl/api/partner/adverts";
ResponseEntity<JsonNode> responseEntity = template.exchange(url, HttpMethod.GET, requestEntity, JsonNode.class);
String adverts = responseEntity.getBody().get("data").toString();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
Advert[] array = objectMapper.readValue(adverts, Advert[].class);
for(Advert a : array) {
advertRepository.save(a);
}
} catch (Exception e) {
System.out.println(e);
}
return "index";
}
what I want to do is to parse json to entity objects and save all adverts objects to sql database in one table.
Method execution stops with exception on this line :
Advert[] array = objectMapper.readValue(adverts, Advert[].class);
I get this error message :
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.lang.String from Array value (token JsonToken.START_ARRAY)
at [Source: (StringReader); line: 24, column: 14] (through reference chain: java.lang.Object[][0]->pl.vida.model.Advert["images"])
Please notice that the field "images" realtes to nested array of json objects.
Please help, I spent one week on this and no result. Thanks
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.lang.String from Array value (token JsonToken.START_ARRAY) at [Source: (StringReader); line: 24, column: 14] (through reference chain: java.lang.Object[][0]->pl.vida.model.Advert["images"])
You are getting the above exception because you are trying to convert an array of objects into a String which is not possible. See in your JSON images & attributes are array of objects.
"images": [{ "url": "https://ireland.apollo.olxcdn.com:443/v1/files/qmvssagjnq1r2-PL/image;s=1000x700" }],
"attributes": [{ "code": "state", "value": "new", "values": null }]
and in your Advert class you have created images & attributes as String types.
private String attributes;
private String images;
Generally, for array kind of object, we make fields either List or Set and if the field is List/Set then we need to create separate classes for them and map as OneToMany relationship. So creating separate classes means separate tables will be created but you don't want to have multiple tables. You want to store all the data in a single table. In a normal case, it is not possible but if we write some additional configuration classes then we can achieve your requirement. These tweaks have been provided by Hibernate itself.
So basically, Hibernate has provided some built-in types like String, Integer, Float, Date, Timezone, etc. Here you can check the complete list of built-in types. But according to our requirements, we can create custom types as well. So to store the array kind of data Hibernate didn't provide any built-in type. Hence we shall create a custom type.
Solution:
So we want to store an array of object data and we can easily store it in com.fasterxml.jackson.databind.JsonNode object. But Hibernate doesn't support this class as a field type. Hence to make supportable for this class we need to write 2 extra classes i.e. JsonNodeStringType.java & JsonNodeStringDescriptor.
JsonNodeStringType.java
public class JsonNodeStringType extends AbstractSingleColumnStandardBasicType<JsonNode> implements DiscriminatorType<JsonNode> {
public static final JsonNodeStringType INSTANCE = new JsonNodeStringType();
public JsonNodeStringType() {
super(VarcharTypeDescriptor.INSTANCE, JsonNodeStringDescriptor.INSTANCE);
}
#Override
public String getName() {
return "JsonNode";
}
#Override
public JsonNode stringToObject(String xml) {
return fromString(xml);
}
#Override
public String objectToSQLString(JsonNode value, Dialect dialect) {
return '\'' + toString(value) + '\'';
}
}
JsonNodeStringDescriptor.java
public class JsonNodeStringDescriptor extends AbstractTypeDescriptor<JsonNode> {
public static final ObjectMapper mapper = new ObjectMapper();
public static final JsonNodeStringDescriptor INSTANCE = new JsonNodeStringDescriptor();
public JsonNodeStringDescriptor() {
super(JsonNode.class, ImmutableMutabilityPlan.INSTANCE);
}
#Override
public String toString(JsonNode value) {
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
#Override
public JsonNode fromString(String string) {
try {
return mapper.readTree(string);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
#Override
public <X> X unwrap(JsonNode value, Class<X> type, WrapperOptions options) {
if (value == null) {
return null;
}
if (String.class.isAssignableFrom(type)) {
return (X) toString(value);
}
throw unknownUnwrap(type);
}
#Override
public <X> JsonNode wrap(X value, WrapperOptions options) {
if (value == null) {
return null;
}
if (String.class.isInstance(value)) {
return fromString(value.toString());
}
throw unknownWrap(value.getClass());
}
}
Now our Advert class will look like as
import org.hibernate.annotations.Type;
#Setter
#Getter
#ToString
#Entity
#Table(name = "advert")
#TypeDef(name = "JsonNode", typeClass = JsonNodeStringType.class)
public class Advert {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ident", unique = true, nullable = false)
private Long ident;
private int id;
private String status;
private String url;
private String created_at;
private String activated_at;
private String valid_to;
private String title;
#Lob
private String description;
private int category_id;
private String advertiser_type;
private Long external_id;
private String external_url;
private String salary;
private String courier;
#Embedded
private Location location;
#Embedded
private Contact contact;
#Embedded
private Price price;
#Type(type = "JsonNode")
private JsonNode images;
#Type(type = "JsonNode")
private JsonNode attributes;
}
Here we go. If you execute the below code it works perfectly.
String advertsString = "[ { \"id\": 643419352, \"status\": \"removed_by_user\", \"url\": \"https://www.olx.pl/d/oferta/opona-12-1-2-x-2-1-4-etrto-62-203-detka-CID767-IDHxILu.html\", \"created_at\": \"2020-11-27 10:46:07\", \"activated_at\": \"2020-12-11 12:41:12\", \"valid_to\": \"2020-12-17 15:38:10\", \"title\": \"opona 12 1/2 \\\" x 2 1/4 etrto 62-203 + dętka\", \"description\": \"opona w bardzo dobrym stanie + dętka, rozmiar 12 1/2 x 2 1/4 , dętka z zaworem samochodowym\", \"category_id\": 1655, \"advertiser_type\": \"private\", \"external_id\": null, \"external_url\": null, \"contact\": { \"name\": \"Damazy\", \"phone\": \"501474399\" }, \"location\": { \"city_id\": 10609, \"district_id\": 301, \"latitude\": \"51.80178\", \"longitude\": \"19.43928\" }, \"images\": [ { \"url\": \"https://ireland.apollo.olxcdn.com:443/v1/files/efa9any4ryrb-PL/image;s=1000x700\" } ], \"price\": { \"value\": \"9\", \"currency\": \"PLN\", \"negotiable\": false, \"budget\": false, \"trade\": false }, \"salary\": null, \"attributes\": [ { \"code\": \"state\", \"value\": \"used\", \"values\": null } ], \"courier\": null }, { \"id\": 643435839, \"status\": \"removed_by_user\", \"url\": \"https://www.olx.pl/d/oferta/opona-4-80-4-00-8-do-taczki-nowa-CID628-IDHxN3p.html\", \"created_at\": \"2020-11-27 11:53:47\", \"activated_at\": \"2020-11-27 11:54:36\", \"valid_to\": \"2020-12-17 15:38:07\", \"title\": \"opona 4.80/4.00 - 8 do taczki nowa!!!\", \"description\": \"opona do taczki, nowa, nigdy nie używana, stan idealny.\\r\\nrozmiar 4.80/4.00-8. \\r\\nopona do taczki, nowa, nigdy nie używana, stan idealny.\\r\\nrozmiar 4.80/4.00-8.\", \"category_id\": 1636, \"advertiser_type\": \"private\", \"external_id\": null, \"external_url\": null, \"contact\": { \"name\": \"Damazy\", \"phone\": \"501474399\" }, \"location\": { \"city_id\": 10609, \"district_id\": 301, \"latitude\": \"51.80178\", \"longitude\": \"19.43928\" }, \"images\": [ { \"url\": \"https://ireland.apollo.olxcdn.com:443/v1/files/qmvssagjnq1r2-PL/image;s=1000x700\" } ], \"price\": { \"value\": \"9\", \"currency\": \"PLN\", \"negotiable\": false, \"budget\": false, \"trade\": false }, \"salary\": null, \"attributes\": [ { \"code\": \"state\", \"value\": \"new\", \"values\": null } ], \"courier\": null } ]";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Advert[] adverts = objectMapper.readValue(advertsString, Advert[].class);
for (Advert advert : adverts) {
Advert saved = advertRepository.save(advert);
System.out.println("saved " + saved.getIdent());
}
I hope your problem gets resolved which you have been stuck with for one week. If you don't want to manually create these types of descriptors you can follow this article to use as an external dependency.
I have JSON paylaod as below:
{
"status": "ok",
"result": {
"result": [
{
"product_id": "1420-131617-82",
"sku": "1420-131617",
"display_sku": "8DD 355 100-411",
"genart_number": 82,
"name": "Bremsscheibe",
"description": null,
"additional_info_text": null,
"product_url": "https://www.autoteile5000.de/product/1420-131617-82",
"image_url": "https://static.autoteile5000.de/product-images/HLP/4625-462502682-3-255-1548045462267.jpg",
"brand": "HELLA PAGID",
"eans": [
"4082300365078"
],
"manufacturer_product_number": "8DD 355 100-411",
"data_supplier_number": "4625",
"pricehammer": false,
"buyable": true,
"bulky_good": false,
"risky_good": false,
"hazardous_good": false,
"car_specific": true,
"has_deposit": false,
"is_exchange_part": false,
"visibility_status": "active",
"deleted": false
}
]
}
}
This is method how I deserialise it:
public List<SimpleProductDto> getProducts(ProductForm productForm) {
JsonParser jsonParser = new JsonParser();
try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("data/product/2210-0929-818/product.json") ) {
String text = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
//System.out.println("print text : " + text);
//Read JSON file
JsonObject objectFromString = jsonParser.parse(text).getAsJsonObject();
// System.out.println(objectFromString.toString());
JsonObject objectFromString1 = objectFromString.getAsJsonObject("result");
// System.out.println(objectFromString.toString());
// System.out.println(objectFromString1.toString());
JsonArray jsonArray = objectFromString1.getAsJsonArray("result");
System.out.println("printing json array : " +jsonArray.toString());
ObjectMapper oMapper = new ObjectMapper();
for(JsonElement element : jsonArray){
JsonObject productObj = element.getAsJsonObject();
System.out.println("printing json object : " + productObj.toString());
SimpleproductDtoMapper productDtoList = oMapper.readValue(productObj.toString(), SimpleproductDtoMapper.class);
}
// List<SimpleproductDtoMapper> productDtoList = oMapper.readValue(jsonArray.toString(), new TypeReference<List<SimpleproductDtoMapper>>() {});
// Map<String, SimpleproductDtoMapper> items = productDtoList.stream().collect(Collectors.toMap(SimpleproductDtoMapper::getProductId, Function.identity()));
//items.forEach((k, v) -> System.out.println("Item : " + k + " Count : " + v));
//Iterate over employee array
//productList.forEach(emp -> parseProductObject((JSONObject) emp));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
This is my POJO:
public class SimpleproductDtoMapper {
#SerializedName("product_id")
private String productId;
private String sku;
#SerializedName("display_sku")
private String displaySku;
#SerializedName("genart_number")
private Integer genartNumber;
private String name;
private String description;
#SerializedName("additional_info_text")
private String additionalInfoText;
#SerializedName("product_url")
private String productUrl;
#SerializedName("image_url")
private String imageUrl;
private String brand;
private List<String> eans;
#SerializedName("manufacturer_product_number")
private String manufacturerProductNumber;
#SerializedName("data_supplier_number")
private String dataSupplierNumber;
private boolean pricehammer;
private boolean buyable;
#SerializedName("bulky_good")
private boolean bulkyGood;
#SerializedName("risky_good")
private boolean riskyGood;
#SerializedName("hazardous_good")
private boolean hazardousGood;
#SerializedName("car_specific")
private boolean carSpecific;
#SerializedName("has_deposit")
private boolean hasDeposit;
#SerializedName("is_exchange_part")
private boolean isExchangePart;
#SerializedName("visibility_status")
private VisibilityStatusDto visibilityStatus;
#SerializedName("deleted")
private boolean deleted;
}
I get the folllowing exception:
at [Source:
(String)"{"product_id":"1420-131617-82","sku":"1420-131617","display_sku":"8DD
355
100-411","genart_number":82,"name":"Bremsscheibe","description":null,"additional_info_text":null,"product_url":"https://www.autoteile5000.de/product/1420-131617-82","image_url":"https://static.autoteile5000.de/product-images/HLP/4625-462502682-3-255-1548045462267.jpg","brand":"HELLA
PAGID","eans":["4082300365078"],"manufacturer_product_number":"8DD 355
100-411","data_supplier_number":"4625","pricehammer":false,"buyable":tr"[truncated
174 chars]; line: 1, column: 16] (through reference chain:
com.kfz24.mockingservice.mapper.SimpleproductDtoMapper["product_id"])
at
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
at
com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:823)
at
com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1153)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1589)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1567)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:294)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at
com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
at com.kfz24.mockingservice.service.impl.MockingProductServiceImpl.getProducts(MockingProductServiceImpl.java:49)
at com.kfz24.mockingservice.controller.MockingProductController.getProducts(MockingProductController.java:29)
Because #SerializedName is from Gson but not Jackson .The equivalent in Jackson is #JsonProperty.
So change all #SerializedName to #JsonProperty. Also , Jackson by default only deserialize the public fields if it does not marked with #JsonProperty.
So for you , I suggest that the simplest way is to make sure all fields are marked with #JsonProperty (e.g. name, brand etc.)
Using jsonschema2pojo you can generate POJO class with Jackson annotations. Choose Jackson 2.x to generate model with Jackson annotations. Source type should be JSON. Below you can see Product class I have generated using this tool. all properties are public, so change them to private and generate getters and setters.
class Product {
#JsonProperty("product_id")
public String productId;
#JsonProperty("sku")
public String sku;
#JsonProperty("display_sku")
public String displaySku;
#JsonProperty("genart_number")
public Integer genartNumber;
#JsonProperty("name")
public String name;
#JsonProperty("description")
public Object description;
#JsonProperty("additional_info_text")
public Object additionalInfoText;
#JsonProperty("product_url")
public String productUrl;
#JsonProperty("image_url")
public String imageUrl;
#JsonProperty("brand")
public String brand;
#JsonProperty("eans")
public List<String> eans = null;
#JsonProperty("manufacturer_product_number")
public String manufacturerProductNumber;
#JsonProperty("data_supplier_number")
public String dataSupplierNumber;
#JsonProperty("pricehammer")
public Boolean pricehammer;
#JsonProperty("buyable")
public Boolean buyable;
#JsonProperty("bulky_good")
public Boolean bulkyGood;
#JsonProperty("risky_good")
public Boolean riskyGood;
#JsonProperty("hazardous_good")
public Boolean hazardousGood;
#JsonProperty("car_specific")
public Boolean carSpecific;
#JsonProperty("has_deposit")
public Boolean hasDeposit;
#JsonProperty("is_exchange_part")
public Boolean isExchangePart;
#JsonProperty("visibility_status")
public String visibilityStatus;
#JsonProperty("deleted")
public Boolean deleted;
#Override
public String toString() {
return "Product{" +
"productId='" + productId + '\'' +
", sku='" + sku + '\'' +
", displaySku='" + displaySku + '\'' +
", genartNumber=" + genartNumber +
", name='" + name + '\'' +
", description=" + description +
", additionalInfoText=" + additionalInfoText +
", productUrl='" + productUrl + '\'' +
", imageUrl='" + imageUrl + '\'' +
", brand='" + brand + '\'' +
", eans=" + eans +
", manufacturerProductNumber='" + manufacturerProductNumber + '\'' +
", dataSupplierNumber='" + dataSupplierNumber + '\'' +
", pricehammer=" + pricehammer +
", buyable=" + buyable +
", bulkyGood=" + bulkyGood +
", riskyGood=" + riskyGood +
", hazardousGood=" + hazardousGood +
", carSpecific=" + carSpecific +
", hasDeposit=" + hasDeposit +
", isExchangePart=" + isExchangePart +
", visibilityStatus='" + visibilityStatus + '\'' +
", deleted=" + deleted +
'}';
}
}
I noticed you wanted to skip result(Object) -> result(Array) levels. See how to do that with Jackson:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
JsonNode jsonNode = mapper
.reader()
.at("/result/result")
.readTree(new FileInputStream(jsonFile));
CollectionType productsType = mapper.getTypeFactory().constructCollectionType(List.class, Product.class);
List<Product> products = mapper.convertValue(jsonNode, productsType);
System.out.println(products);
}
}
Above code prints:
[Product{productId='1420-131617-82', sku='1420-131617', displaySku='8DD 355 100-411', genartNumber=82, name='Bremsscheibe', description=null, additionalInfoText=null, productUrl='https://www.autoteile5000.de/product/1420-131617-82', imageUrl='https://static.autoteile5000.de/product-images/HLP/4625-462502682-3-255-1548045462267.jpg', brand='HELLA PAGID', eans=[4082300365078], manufacturerProductNumber='8DD 355 100-411', dataSupplierNumber='4625', pricehammer=false, buyable=true, bulkyGood=false, riskyGood=false, hazardousGood=false, carSpecific=true, hasDeposit=false, isExchangePart=false, visibilityStatus='active', deleted=false}]
I have a JSON-String array, where its entry has the following properties, firstName, lastName, loginName, Country, phoneNumber, and status. Here's an example
[
{
"firstName": "Patrick",
"lastName": "Smith",
"loginName":"test0003#test.com",
"Country":"US",
"phoneNumber": "287 125-1434",
"status": "340"
},
{
"firstName": "Bob",
"lastName": "Williams",
"loginName":"test0002#test.com",
"Country":"US",
"phoneNumber": "213 111-9943",
"status": "215"
},
{
"firstName": "John",
"lastName": "Johnson",
"loginName":"test0001#test.com",
"Country":"DE",
"phoneNumber": "212 555-1234",
"status": "167"
},
{
"firstName": "George",
"lastName": "Jones",
"loginName":"test0004#test.com",
"Country":"FR",
"phoneNumber": "217 987-2634",
"status": "340"
}
]
Now, I want to search for a specific entry based on the properties loginName and status
For example
loginName: test0001#test.com
status: 167
{
"firstName": "John",
"lastName": "Johnson",
"loginName":"test0001#test.com",
"Country":"DE",
"phoneNumber": "212 555-1234",
"status": "167"
}
What would be the most optimized solution?
I use JSONObject and here is another way to parse.
1. Parse using JSONArray
2. Loop the array and read into UserProfile objects
3. Store it in HashMap to get using key
import java.util.HashMap;
import java.util.Map;
import org.json.*;
class UserProfile{
String loginName;
int status;
String firstName;
String key;
UserProfile(){
}
String getLoginName(){
return loginName;
}
String getFirstName(){
return firstName;
}
void setKey(String key){
this.key = key;
}
void setLoginName(String loginName){
this.loginName = loginName;
}
void setStatus(int status){
this.status = status;
}
void setFirstName(String firstName){
this.firstName = firstName;
}
}
public class JSONObjParser {
public static void main(String[] args){
Map<String, UserProfile> map = new HashMap<String, UserProfile>();
String msg ="[{ firstName: Patrick, lastName: Smith, loginName:test0003#test.com, Country:US, phoneNumber: 287 125-1434, status: 340 }, { firstName: Bob, lastName: Williams, loginName:test0002#test.com, Country:US, phoneNumber: 213 111-9943, status: 215 }]";
JSONArray jsonarray = new JSONArray(msg);
for (int i = 0; i < jsonarray.length(); i++) {
JSONObject jsonobject = jsonarray.getJSONObject(i);
String loginName = jsonobject.getString("loginName");
int status = jsonobject.getInt("status");
String firstName = jsonobject.getString("firstName");
UserProfile profile = new UserProfile();
profile.setFirstName(firstName);
profile.setLoginName(loginName);
profile.setStatus(status);
String key = loginName + Integer.toString(status);
map.put(key, profile);
}
for (String key : map.keySet()) {
UserProfile profile = map.get(key);
System.out.println("Key = " + key + ", FirstName = " + profile.getFirstName());
}
}
}
Using Jackson, this is the crudest snippet I can think of:
private static ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) throws IOException {
System.out.println(filterJsonArray(JSON, "loginName", "test0001#test.com", "status", "167"));
}
public static String filterJsonArray(String array, String keyOne, Object valueOne, String keyTwo, Object valueTwo) throws IOException {
Map[] nodes = mapper.readValue(array, HashMap[].class);
for (Map node : nodes) {
if (node.containsKey(keyOne) && node.containsKey(keyTwo)) {
if (node.get(keyOne).equals(valueOne) && node.get(keyTwo).equals(valueTwo)) {
return mapper.writeValueAsString(node);
}
}
}
return null;
}
Of course it will only returns the first match to the given pairs. If you need all the values, make it return a list instead and populate it inside the loop.
I want to parse the below JSON into POJO. I am using jackson to parse the json.
{
"totalSize": 4,
"done": true,
"records": [
{
"attributes": {
"type": "oppor",
"url": "/service/oppor/456"
},
"AccountId": "123",
"Id": "456",
"ProposalID": "103"
}
]
}
In the above JSON, the fields "totalSize", "done", "records" and "attributes" are known fields. Whereas, "AccountId", "Id" and "ProposalID" are unknown fields. And in the above JSON, I don't need "attributes" to be part of my bean object.
And here is equivalent bean class for my JSON
public class Result {
private int totalSize;
private boolean done;
private List<Map<String, String>> records;
public int getTotalSize() {
return totalSize;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
public List<Map<String,String>> getRecords() {
return records;
}
public void setRecords(List<Map<String, String>> records) {
this.records = records;
}
}
Hence there are unknown fields in the records element I just used List to get the results element in bean. Here in this Map, I don't want the field "attributes". How can I ignore this while parsing?
And below is the exception that I am getting as attributes is not a string element.
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: [B#66fdec9; line: 1, column: 40] (through reference chain: com.sample.json.Result["records"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:691)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:46)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringMap(MapDeserializer.java:430)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:312)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:26)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:227)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:204)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23)
UPDATE 2015/08/29:
As you have commented that
I achieved dynamic field support by parsing the JSON into map. Ignoring bad JSON element is what pending
I suggest that you should process original JSONObject to remove the "attributes" element from it.
Original JSONObject, for example:
{
"totalSize": 4,
"done": true,
"records": [
{
"attributes": {
"type": "oppor",
"url": "/service/oppor/456"
},
"AccountId": "123",
"Id": "456",
"ProposalID": "103"
}
]
}
After process, new JSONObject will be like the following:
{
"records": {
"AccountId": "123",
"Id": "456",
"ProposalID": "103"
},
"totalSize": 4,
"done": true
}
Use the code as the following:
JSONObject jsonObject;
try {
jsonObject = new JSONObject(jsonString1);
JSONArray jsonArray = new JSONArray(jsonObject.get("records").toString());
JSONObject jsonObject1 = jsonArray.getJSONObject(0);
jsonObject1.remove("attributes");
jsonObject.put("records", jsonObject1);
} catch (JSONException e) {
e.printStackTrace();
}
Then, use your own code that achieved dynamic field support by parsing the JSON into map.
END OF UPDATE 2015/08/29
I suggest that you use Gson and transient in this case
Like this
String jsonString1 = "{\n" +
" \"totalSize\": 4,\n" +
" \"done\": true,\n" +
" \"records\": [\n" +
" {\n" +
" \"attributes\": {\n" +
" \"type\": \"oppor\",\n" +
" \"url\": \"/service/oppor/456\"\n" +
" },\n" +
" \"AccountId\": \"123\",\n" +
" \"Id\": \"456\",\n" +
" \"ProposalID\": \"103\"\n" +
" }\n" +
" ]\n" +
"}";
Gson gson = new Gson();
Result result1 = gson.fromJson(jsonString1, Result.class);
Your classes, pay attention to transient:
public class Result {
private int totalSize;
private boolean done;
private List<Record> records;
}
public class Record {
private transient Map<String, String> attributes;
private int AccountId;
private int Id;
private int ProposalID;
}
You will get the result:
P/S: I tested in Android Studio :)
UPDATE:
String jsonString1 = "{\n" +
" \"totalSize\": 4,\n" +
" \"done\": true,\n" +
" \"records\": [\n" +
" {\n" +
" \"attributes\": {\n" +
" \"type\": \"oppor\",\n" +
" \"url\": \"/service/oppor/456\"\n" +
" },\n" +
" \"AccountId\": \"123\",\n" +
" \"Id\": \"456\",\n" +
" \"ProposalID\": \"103\"\n" +
" }\n" +
" ]\n" +
"}";
Gson gson = new Gson();
Object object = gson.fromJson(jsonString1, Object.class);
Map<String, String> stringMap = (Map<String, String>) object;
Result myResult = new Result();
Iterator entries = stringMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
String key = entry.getKey().toString();
String value = entry.getValue().toString();
switch (key) {
case "totalSize":
myResult.totalSize = (int) Double.parseDouble(entry.getValue().toString());
break;
case "done":
myResult.done = Boolean.valueOf(entry.getValue().toString());
break;
case "records":
try{
Object object1 = entry.getValue();
List<Object> objectList = (List<Object>) object1;
Map<String, Object> stringMap2 = (Map<String, Object>) objectList.get(0);
Map<String, String> recordMap = new HashMap<>();
Iterator entries2 = stringMap2.entrySet().iterator();
while (entries2.hasNext()) {
Map.Entry entry2 = (Map.Entry) entries2.next();
String key2 = entry2.getKey().toString();
String value2 = entry2.getValue().toString();
if (!"attributes".equals(key2)) {
recordMap.put(key2, value2);
}
entries2.remove();
}
myResult.records = recordMap;
} catch (Exception e) {
e.printStackTrace();
}
break;
}
entries.remove();
}
Classes:
public class Result {
private int totalSize;
private boolean done;
private Map<String, String> records;
}
Debug result:
1) Create a Record class object
2) Add #JsonIgnore Annotation on fields you won't
public class Result {
private int totalSize;
private boolean done;
private Record records;
[..]
}
public class Record {
#JsonIgnore
private Map<String, String> attributes;
private int accountID;
private int id;
private int approvalID;
[..]
}
Create a new POJO class for attributes,
public class Result {
private int totalSize;
private boolean done;
private List<Attributes> records;
// Your Getters & Setters
}
public class Attributes{
List<Map<String,String>> attributes;
// Add other variables if necessary like AccountId, etc.,
// Your Getters & Setters
}
I would suggest to use [Google gson API][1]'s #Expose annotation. (if that is allowed in your environment).
You can simply annotate the fields(with #Expose) which are required in your generated json file, and leave it other fields. And during generating json, use API method, excludeFieldsWithoutExposeAnnotation.
Sample example can be seen here.
Note : In your example, treat your Result as Main POJO, and records is another POJO which has attributes,accountId etc fields.
Then there is has-a relationship (Java composition) between them.
And after that, you can invoke Json to pojo conversion like below--
com.google.gson.Gson gson = new com.google.gson.GsonBuilder()
.excludeFieldsWithoutExposeAnnotation().create();
Result result= gson.fromJson(yourjsonString, Result.class);
If you have specific fields you don't want to map, u can use #JsonIgnore annotation over the field name
public class MyJsonObj{
#JsonProperty("name")
String fieldName
#JsonIgnore
String fieldNameIgnored;
}
If you want to ignore all the fields not mentioned in your POJO, you can use
#JsonIgnoreProperties annotation over class
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyJsonObj{
strong text
}