Handling empty JSON - java

I need to send following JSON in API BODY POST request:
{
"name": "",
"type": "TEMP",
"shared": false,
"search": {
},
"order": [
]
}
In my MainBody.java, declared
private String name;
private String type;
private boolean shared;
private JSON search;
private Object order;
and defined getters and setters.
In Payload.java,
MainBody mb = new MainBody();
mb.setName("");
mb.setType("TEMP");
mb.setShared(false);
mb.setSearch(null);
mb.setOrder(new ArrayList<>());
ObjectMapper om = new ObjectMapper();
String myData = om.writerWithDefaultPrettyPrinter().writeValueAsString(mb);
System.out.println(myData);
results
{
"name" : "",
"type" : "TEMP",
"shared" : false,
"search" : null,
"order" : [ ]
}
Please assist with how search as { } can be achieved as per expected JSON instead of null.
TIA.

Instead of setting search to null, you need to set it to an empty object. I'm not sure which JSON library you are using, but there should be an object constructor like new JsonObject(). Depending on what the allowed values for search are, you may also want to consider representing it in your class as Map<String, String> or something like that.

I would try something like this:
mb.setSearch(new JSON());
This way you create empty object and there should be only {}. It also depends on which JSON library do you use.

Issue resolved after using JSONMapper.

Related

Using Jackson to extract information from Property names

I currently receive the following JSON body
{
"productId": "90000011",
"offerId": "String",
"format": "String",
"sellerId": "String",
"sellerName": "String",
"shippingPrice[zone=BE,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=BE,method=STD]": 1,
"deliveryTimeLatestDays[zone=BE,method=STD]": 1,
"shippingPrice[zone=NL,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=NL,method=STD]": 1,
"deliveryTimeLatestDays[zone=NL,method=STD]": 1
}
As you can see, I have similar properties that differ by zone and method enclosed in square brackets. I don't want to change the code every time a new zone and/or method is introduced. I'm looking for a more dynamic way you deserialize this via Jackson.
Is there a way to automatically deserialize all properties starting with shippingPrice, deliveryTimeEarliestDays and deliveryTimeLatestDays into the following format?
{
"productId": "90000011",
"offerId": "String",
"format": "String",
"sellerId": "String",
"sellerName": "String",
"deliveryModes":[
{
"method":"STD"
"zone":"BE",
"shippingPrice":0.0,
"deliveryTimeEarliestDays":1,
"deliveryTimeLatestDays":1
},{
"method":"STD"
"zone":"NL",
"shippingPrice":0.0,
"deliveryTimeEarliestDays":1,
"deliveryTimeLatestDays":1
}]
}
My first idea was to use the #JsonAnySetter annotation and put everything in a Map but that still leaves me with manual parsing of the field name.
My Second Idea was to build a custom deserializer where I loop over all attributes and filter out all the ones that start with shippingPrice, deliveryTimeEarliestDays and deliveryTimeLatestDays and map them to the described format above.
In order to achieve the required result, you need to implement deserialization logic yourself, it can't be done only by sprinkling a couple of data binding annotations.
That's how it can be done.
Assume here's a POJO that corresponds to your input JSON (to avoid boilerplate code, I'll use Lombok annotations):
#Getter
#Setter
public static class MyPojo {
private String productId;
private String offerId;
private String format;
private String sellerId;
private String sellerName;
#JsonIgnore // we don't want to expose this field to Jackson as is
private Map<DeliveryZoneMethod, DeliveryMode> deliveryModes = new HashMap<>();
#JsonAnySetter
public void setDeliveryModes(String property, String value) {
DeliveryZoneMethod zoneMethod = DeliveryZoneMethod.parse(property);
DeliveryMode mode = deliveryModes.computeIfAbsent(zoneMethod, DeliveryMode::new);
String name = property.substring(0, property.indexOf('['));
switch (name) {
case "shippingPrice" -> mode.setShippingPrice(new BigDecimal(value));
case "deliveryTimeEarliestDays" -> mode.setDeliveryTimeEarliestDays(Integer.parseInt(value));
case "deliveryTimeLatestDays" -> mode.setDeliveryTimeLatestDays(Integer.parseInt(value));
}
}
public Collection<DeliveryMode> getModes() {
return deliveryModes.values();
}
}
Properties productId, offerId, format, sellerId, sellerName would be parsed by Jackson in a regular way.
And all other properties formatted like "shippingPrice[zone=BE,method=STD]" would be handled by the method annotated with #JsonAnySetter.
To facilitate extracting and storing information from such properties I've defined a couple of auxiliary classes:
DeliveryZoneMethod which contains information about a zone and delivery method as its name suggests (the purpose of this class is to serve as Key in the map deliveryModes).
DeliveryMode which is meant to contain all the need information that correspond to a particular zone and method of delivery.
For conciseness, DeliveryZoneMethod can be implemented as a Java 16 record:
public record DeliveryZoneMethod(String method, String zone) {
public static Pattern ZONE_METHOD = Pattern.compile(".+zone=(\\p{Alpha}+).*method=(\\p{Alpha}+)");
public static DeliveryZoneMethod parse(String str) {
// "shippingPrice[zone=BE,method=STD]" - assuming the given string has always the same format
Matcher m = ZONE_METHOD.matcher(str);
if (!m.find()) throw new IllegalArgumentException("Unable to parse: " + str);
return new DeliveryZoneMethod(m.group(1), m.group(2));
}
}
And here's how DeliveryMode might look like:
#Getter
#Setter
public static class DeliveryMode {
private String method;
private String zone;
private BigDecimal shippingPrice;
private int deliveryTimeEarliestDays;
private int deliveryTimeLatestDays;
public DeliveryMode(DeliveryZoneMethod zoneMethod) {
this.method = zoneMethod.method();
this.zone = zoneMethod.zone();
}
}
Usage example:
public static void main(String[] args) throws JsonProcessingException {
String json = """
{
"productId": "90000011",
"offerId": "String",
"format": "String",
"sellerId": "String",
"sellerName": "String",
"shippingPrice[zone=BE,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=BE,method=STD]": 1,
"deliveryTimeLatestDays[zone=BE,method=STD]": 1,
"shippingPrice[zone=NL,method=STD]": 0.0,
"deliveryTimeEarliestDays[zone=NL,method=STD]": 1,
"deliveryTimeLatestDays[zone=NL,method=STD]": 1
}
""";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo = mapper.readValue(json, MyPojo.class);
String serializedJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(myPojo);
System.out.println(serializedJson);
}
Output:
{
"productId" : "90000011",
"offerId" : "String",
"format" : "String",
"sellerId" : "String",
"sellerName" : "String",
"modes" : [ {
"method" : "BE",
"zone" : "STD",
"shippingPrice" : 0.0,
"deliveryTimeEarliestDays" : 1,
"deliveryTimeLatestDays" : 1
}, {
"method" : "NL",
"zone" : "STD",
"shippingPrice" : 0.0,
"deliveryTimeEarliestDays" : 1,
"deliveryTimeLatestDays" : 1
} ]
}
I would go with your first idea to deserialize your JSON into a map. And yes you will still need to analyze the map keys. It is easy to deserialize Json into a map with Json Jackson, but there is an Open source library called MgntUtils that provides class JsonUtils which is a thin wrapper over Json-Jackson library. using it you can very simply deserialize your Json into a Map (or any other class). Your code would look like this:
try {
Map<String, Object> map = JsonUtils.readObjectFromJsonString(jsonStr, Map.class);
System.out.println(map);
} catch (IOException e) {
...
}
Here is Javadoc for JsonUtils. The library can be obtained as maven artifact or on Github (with source code and Javadoc).
Disclaimer: This library is written and maintained by me

ElasticSearch Make Field non-searchable from java

I am currently working on elastic search through my java Application . I know how to index the Java pojo using RestHighLevelClient. How i can make search only on new fields not the complete pojo.?
public class Employee{
private long id;
private String name;
private String designation;
private String address; //want to index but not searchable in elastic search
}
My Code for indexing is below which is working fine:
public String saveToEs(Employee employee) throws IOException {
Map<String, Object> map = objectMapper.convertValue(employee, Map.class);
IndexRequest indexRequest =
new IndexRequest(INDEX, TYPE, employee.getId().toString()).source(map, XContentType.JSON);
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
I need to do this in java .Any help please or good link ?
Writing another answer for RestHighLevelClient As another answer is useful for people not using the Rest client and adding this in the first answer makes it too long.
Note: you are passing the type which is deprecated in ES 7.X and I am using the ES 7.X version, so my code is according to 7.X.
CreateIndexRequest request = new CreateIndexRequest("employee");
Map<String, Object> name = new HashMap<>();
name.put("type", "text");
Map<String, Object> address = new HashMap<>();
address.put("type", "text");
address.put("index", false);
Map<String, Object> properties = new HashMap<>();
properties.put("name", name);
properties.put("address", address);
Map<String, Object> mapping = new HashMap<>();
mapping.put("properties", properties);
request.mapping(mapping);
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
Important points
I've used only 2 fields for illustration purpose, one of which is address field which is not searchable, and to do that I used, address.put("index", false); , while name is searchable field and there this option isn't present.
I've created index mapping using the Map method which is available in this official ES doc.
you can check the mapping created by this code, using mapping REST API.
Below is the mapping generated for this code in my system and you can see, index: false is added in the address field.
{
"employee": {
"mappings": {
"properties": {
"address": {
"type": "text",
"index": false
},
"name": {
"type": "text"
}
}
}
}
}
You can just use the same search JSON mentioned in the previous answer, to test that it's not searchable.
Use the index option as false on the address field, which is by default true to make it unsearchable. As mention in the same official ES link:
The index option controls whether field values are indexed. It accepts
true or false and defaults to true. Fields that are not indexed are
not queryable.
Let me show you how can you test it using the REST API and then the java code(using rest-high level client) to accomplish it.
Mapping
{
"mappings": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text"
},
"designation": {
"type": "text"
},
"address": {
"type": "text",
"index" : false --> made `index` to false
}
}
}
}
Index few docs
{
"address" : "USA",
"name" : "Noshaf",
"id" : 234567892,
"designation" : "software engineer"
}
{
"address" : "USA california state",
"name" : "opster",
"id" : 234567890,
"designation" : "software engineer"
}
A simple match search query in JSON format on address field
{
"query": {
"match" : {
"address" : "USA"
}
}
}
Exception from Elasticsearch clearly mention, it's not searchable
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Cannot search on field [address] since it is not indexed."
}

Parse JSON with JsonArray and HashMap

I need to parse the following JSON and add values from it into three different Java objects. I was thinking to form other 3 Jsons in order to do this. I have some issues with parsing, as the JSON is a little bit complicated. The JSON is below:
{
"totalCount": 1,
"results": [
{
"teleCommunications": [
{
"areaCode": "100",
"telephoneNumber": "300-2444",
"internationalAreaCode": "",
"communicationType": 1
},
{
"areaCode": "100",
"telephoneNumber": "200-2555",
"internationalAreaCode": "",
"communicationType": 5
}
],
"delegate": {
"id": 0,
"range": 0,
},
"name": "Andrew",
"composedKey": {
"id": 615,
"range": 50,
},
"isBranchAddress": false,
"emailAddresses": [
{
"emailAddressType": 9,
"emailAddress": "andrew.brown#gmail.com"
}
],
"name": "Brown",
"zipCodeCity": "65760 Leipzig",
"salutation": "Mr.",
"openingDate": "2019-09-20",
"streetHouseNumber": "Offenbach. 37",
"modificationTimestamp": "2018-01-27"
}
]
}
I need to get separately the values from name, zipCodeCity, salutation, openingDate, streetHouseNumber in a JSON ( or any other way) , emailAddresses in a different JSON and the other data in another one.
I tried with this piece of code:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
HashMap<String, Object> result = new ObjectMapper().readValue(response, HashMap.class);
Object object = result.get("results");
List<HashMap<String, String>> jsonValues = (List<HashMap<String, String>>)object;
for(String key : jsonValues.get(0).keySet()){
System.out.println("name value " + jsonValues.get(0).get("name"));
System.out.println("zip code City " + jsonValues.get(0).get("zipCodeCity"));
}
The problem is that I would not go for such a hardcoded way...it is not very suitable.
Does anyone know a better approach for storing the values I need and parsing the Json more optimally?
Thank you
Look for this link, there's a demo application specially for you: https://github.com/nalmelune/jackson-demo-58591850 (look at JacksonTest)
First of all, you'll need data-classes for this (or so called dto), representing structure. You can use online generators for that (google "json to java dto online") or do it yourself. For example, here's root object (see github link for more):
public class Root {
private int totalCount;
private List<Results> results;
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getTotalCount() {
return this.totalCount;
}
public void setResults(List<Results> results) {
this.results = results;
}
public List<Results> getResults() {
return this.results;
}
}
Then configure your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
And use it (don't create new mapper, as it is in your example):
Root result = mapper.readValue(response, Root.class);
Finally, you can access it as usual Plain Old Java Objects:
for (Results resultItem : result.getResults()) {
System.out.println(resultItem.getSalutation());
System.out.println(resultItem.getName());
System.out.println(resultItem.getZipCodeCity());
System.out.println(resultItem.getOpeningDate());
System.out.println(resultItem.getStreetHouseNumber());
}
To make it work be sure validate your json (there were invalid commas after "range", and "name" comes twice and so on, it fixed on github). And be sure to include jsr310 in classpath by adding com.fasterxml.jackson.datatype:jackson-datatype-jsr310 module. You will need it for LocalDate and LocalDateTime objects.
You may create java class something like below
#Data
class MyCustomClass{
int totalCount;
List<TeleCommunicationDetail> results;
// other props
}
Next you can add attributes to TeleCommunicationDetail and finally you can use MyCustomClass reaonseInJavaObject = objectMapper.readValue(myJson,MyCustomClass.class);
Refer to this question for details.
Your approach is correct, as you simply read your JSON object as Map<String, Object> which always will work (as long as JSON Object is valid). And then you retrieve your values that you expect to be there. Another approach is to create a Class that maps to your original JSON and parse JSON with ObjectMapper into your class. Then you can retrieve your data with your class setters and getters. In this case, you don't need to use key Strings like "results" etc but you have to ensure that your JSON is not only valid JSON but always conforms to your class. Pick your option...

JSON To ArrayList<CustomObject> With GSON While Ignoring Certain Fields

I am looking to create an ArrayList of custom objects from a JSON feed using GSON. My current approach works fine for a single JSON object holding an array, but now I need to parse a more complex JSON object. The first JSON feed looked like this:
{"data":
{"item_id": "1", "element": "element1"}
{"item_id": "2", "element": "element2"}
{"item_id": "3", "element": "element3"}
...
}
My method for extracting each item was using a simple custom object and parsing the JSON into an ArrayList of these objects.
InputStreamReader input = new InputStreamReader(connection.getInputStream());
Type listType = new TypeToken<Map<String, ArrayList<CustomObject>>>(){}.getType();
Gson gson = new GsonBuilder().create();
Map<String, ArrayList<CustomObject>> tree = gson.fromJson(input, listType);
ArrayList<CustomObject> = tree.get("data");
The current JSON object looks like this:
{"rate_limit": 1, "api_version": "1.2", "generated_on": "2015-11-05T19:34:06+00:00", "data": [
{"collection": [
{"item_id": "1", "time": "2015-11-05T14:40:55-05:00"},
{"item_id": "2", "time": "2015-11-05T14:49:09-05:00"},
{"item_id": "3", "time": "2015-11-05T14:51:55-05:00"}
], "collection_id": "1"},
{"collection": [
{"item_id": "1", "time": "2015-11-05T14:52:01-05:00"},
{"item_id": "2", "time": "2015-11-05T14:49:09-05:00"},
{"item_id": "3", "time": "2015-11-05T14:51:55-05:00"}
], "collection_id": "2"
]}
And I am having trouble parsing it because of the mixed types of data, some are numbers, strings and lastly arrays. I have a custom object built that takes an array of another custom object. This is the collection object:
public class CustomCollection {
private String collection_id;
private ArrayList<CustomItem> collection_items = new ArrayList<>();
public CustomCollection() {
this(null, null);
}
public CustomCollection(String id, ArrayList<CustomItem> items) {
collection_id = id;
collection_items = items;
}
public String getId() {
return collection_id;
}
public ArrayList<CustomItem> getItems() {
return collection_items;
}
}
And this is the item object:
public class CustomItem {
private String item_id;
private String item_element;
public CustomItem() {
this(null, null);
}
public CustomItem(String id, String element) {
item_id = id;
item_element = element;
}
public String getId() {
return item_id;
}
public String getElement() {
return item_element;
}
}
I do not really care about obtaining the other elements (i.e. "rate_limit", "api_version", "generated_on"), I just want to pass the "data" element into an ArrayList of objects. But when I try something similar to my original method, the parser stops a the first object because it receives a number instead of an array. Resulting in an IllegalStateException: Expected BEGIN_ARRAY but was NUMBER at line 1 column 17 path $.. I would like to know how I can either get the parser to ignore the other elements, or how I can get each element separately using GSON.
EDIT: The proposed solution to my problem, found in Ignore Fields When Parsing JSON to Object, technically does solve my issue. But this seems like a lengthy process that is unnecessary in my case. I have found a much simpler solution to my problem, posted in my answer below. I am also unsure if this method would work well for the aforementioned question, considering there does not seem to be a way to get a JsonObject from a JsonArray by key name in GSON.
The solution I have found is to parse the data into a com.google.gson.JsonObject, then into a JsonArray by key name. I can then use this JsonArray as a parameter in Gson.fromJson() to extract the data into my ArrayList of custom objects.
InputStreamReader input = new InputStreamReader(connection.getInputStream());
JsonArray data = new JsonParser().parse(input).getAsJsonObject().getAsJsonArray("data");
Type listType = new TypeToken<ArrayList<CustomCollection>>(){}.getType();
ArrayList<CustomCollection> collection = new Gson().fromJson(data, listType);
input.close();
This method ignores all other JSON fields, only obtaining the one specified. It also takes the array of objects from within the "data" object and puts them into the ArrayList of custom objects within CustomCollection.

Injecting additional fields in serialization

I have objects which I serialize using Jackson. e.g.
class A {
int id;
String value
}
is serialized to:
{
"id": 1,
"value": "test"
}
Now I want an additional field in the serialized version:
{
"id": 1,
"value": "test"
"__errors__": [
{ field: "id", "message": "already in use" },
{ field: "value", "message": "field to long" },
]
}
This field is composed by a list of class Error:
class Error {
String field;
String message;
}
This should be done while serializing and work without adding a method / property to the class A.
So is there a way to inject additional fields into jackson mapper? Best would be a generic method which would work for all classes passed to the mapper without additional code.
(Background: I want to use this to return validation errors to AngularJS within the normal result. Because putting both in a container would complicate the code on the client side.)
Thanks in advance!
EDIT:
I imagine the final usage could look something like this:
ObjectMapper mapper = ...;
PrintWriter out = ...;
// Normally the parsed user input
A a = new A( 5, "test" );
// Normally generated by validation of user input
List<Error> errors = Arrays.asList( new Error( "id", "already in use" ) );
// No write it as combined json
mapper.writeValue( out, a, error );
// ... or ...
customWrite( out, a, error );
EDIT2:
Currently I'm looking at two possible solutions:
Write a custom Serializer. Seems like a clean solution but needs to do all the stuff the default serializer does. But is a complex solution because I first have to figure out how custom serialization and extending the default works.
Just use String replacement and concatenation. Write both objects seperately and remove staring/closing curly braces and append one on another. Quick, but very dirty. (I really don't like this but it could work.)
You can easily do that using ObjectMapper.convertValue method. See below example:
A a = new A(101, "test");
List<Error> errors = Arrays.asList(new Error("id", "already in use"), new Error("value",
"field to long"));
// Create Jackson objects
ObjectMapper jsonMapper = new ObjectMapper();
MapType mapType = jsonMapper.getTypeFactory().constructMapType(LinkedHashMap.class,
String.class, Object.class);
// Create map
LinkedHashMap<String, Object> map = jsonMapper.convertValue(a, mapType);
map.put("__errors__", errors);
// Serialize map
String json = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(map);
System.out.println(json);
Above example prints:
{
"id" : 101,
"value" : "test",
"__errors__" : [ {
"field" : "id",
"message" : "already in use"
}, {
"field" : "value",
"message" : "field to long"
} ]
}

Categories

Resources