Java JSON Processing - java

I'm having a hard time processing the below JSON with Java, which is being returned from on an external Ansible playbook:
{"Sample":
{
"tag_description":"abc","tag_category_id":"def","tag_id":"ghi"
},
"Sample1":
{
"tag_description":"jkl","tag_category_id":"mno","tag_id":"pqr"
}
}
I've been able to successfully parse one section of the JSON using a custom deserializer, though it only ever gets the first section. Any ideas are hugely appreciated.
#JsonComponent
public class TagSerializer extends JsonDeserializer<Tag> {
#Override
public Tag deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException,
JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
JsonFactory factory = mapper.getFactory();
JsonNode treeNode = jsonParser.getCodec().readTree(jsonParser);
Iterator<Map.Entry<String, JsonNode>> fields = treeNode.fields();
String name = "";
// collect the tag name
Map.Entry<String, JsonNode> entry = fields.next();
name = entry.getKey();
// now that we have the tag name, parse it as a separate JSON object
JsonNode node = entry.getValue();
// get the values from the JSON
String description = node.get("tag_description").asText();
String category_id = node.get("tag_category_id").asText();
String tag_id = node.get("tag_id").asText();
return new Tag(name, category_id, description, tag_id);
}
}
I'm calling the method from a Spring Boot REST API endpoint, and my 'tag' model is a Spring entity:
'Tag' model:
#Entity
#JsonDeserialize(using = TagSerializer.class)
public class Tag {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String tag_category_id;
private String tag_description;
private String tag_id;
//JPA requires that a default constructor exists
//for entities
protected Tag() {}
public Tag(String name,
String tag_category_id,
String tag_description,
String tag_id) {
this.name = name;
this.tag_category_id = tag_category_id;
this.tag_description = tag_description;
this.tag_id = tag_id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTag_category_id() {
return tag_category_id;
}
public void setTag_category_id(String tag_category_id) {
this.tag_category_id = tag_category_id;
}
public String getTag_description() {
return tag_description;
}
public void setTag_description(String tag_description) {
this.tag_description = tag_description;
}
public String getTag_id() {
return tag_id;
}
public void setTag_id(String tag_id) {
this.tag_id = tag_id;
}
public String toString() {
return "<Tag:[Name: " + this.name + "],[tag_category: "+
this.tag_category_id + "],[tag_description: "+
this.tag_description + "],[tag_id:"+this.tag_id+"]";
}
}
Spring Boot endpoint:
#PostMapping(value="/store", consumes = APPLICATION_JSON_VALUE)
public void tagJson(#RequestBody String json) {
// delete any existing tags
tagRepository.deleteAll();
//lets modify the json to make it look nicer
String modjson = "["+json+"]";
ObjectMapper mapper = new ObjectMapper();
try {
Tag[] tags = mapper.readValue(modjson, Tag[].class);
for (Tag t : tags)
tagRepository.save(t);
} catch (Exception exception) {
exception.printStackTrace();
}
}

If you are using Spring MVC consider explicitly declare desired type when referreing to #RequestBody and let the framework do the job for you
#PostMapping(value="/store", consumes = APPLICATION_JSON_VALUE)
public void tagJson(#RequestBody Map<String, Tag> json) {
// Do not mess with ObjectMapper here, Spring will do the thing for you
}

This isn't a direct answer but a guide in a possible direction, using Gson.
package test;
import java.util.Map;
import com.google.gson.Gson;
public class JsonTest {
public static void main(final String... args) {
new JsonTest().run();
}
public void run() {
final Gson gson = new Gson();
final Map<?, ?> result = gson.fromJson("{" +
" \"Sample\": {" +
" \"tag_description\": \"abc\"," +
" \"tag_category_id\": \"def\"," +
" \"tag_id\": \"ghi\"" +
" }," +
" \"Sample1\": {" +
" \"tag_description\": \"jkl\"," +
" \"tag_category_id\": \"mno\"," +
" \"tag_id\": \"pqr\"" +
" }" +
"}", Map.class);
System.out.println("Map size: " + result.size());
}
}
The resulting size is 2. The map entries are keyed Sample, Sample1, and the values are lists containing the nodes. You can see this using a debugger.

Related

Java - Extract array String from JSON and convert to JSON format

I have a JSON String as below:
"{ \"password\":\"des123\",\"ROU_DATA\":[{\"FORM_RECEIVING_TIME\":\"12:00:00\",\"REMARKS\":\"Redemption of Unit\"}, {\"FORM_RECEIVING_TIME\":\"13:00:00\",\"REMARKS\":\"sALE of Unit\"}] }";
Now I want to extract the Array from it and need to use it as a separate pojo class so that I can iterate over each value..
Now the problem is, when I try to convert the complete String to Map and get the Array value from the map.. It transforms its format to MAp format like:
{FORM_RECEIVING_DATE = 12:00:00, etc..}
However json string should be {"FORM_RECEIVING_DATE": "12:00:00", etc..}
due to the MAp format its now allowing me to parse it using my POJO Class..
Please help to convert it to my JSONFormat ...
**NOTE: Please note that I can only use Jackson **.
CLASS A
ObjectMapper mapper2 = new ObjectMapper();
Map<String, Object> map;
map = mapper2.readValue(json, new TypeReference<Map<String, Object>>(){});
System.out.println("map: " + map.get("ROU_DATA") );
String array = map.get("ROU_DATA").toString();
String json2 = new ObjectMapper().writeValueAsString(array.replace("[", "").replace("]", ""));
String json3 = new ObjectMapper().writeValueAsString(json2);
System.out.println("json2>>" + json2);
System.out.println("json2>>" + json3);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 1. convert JSON array to Array objects
ROU[] pp1 = mapper.readValue("{" + array.replace("=", ":") + "}", ROU[].class);
for (ROU person : pp1) {
System.out.println(person.getRemarks());
}
CLASS B
import com.fasterxml.jackson.annotation.JsonProperty;
public class ROU {
#JsonProperty("FORM_RECEIVING_TIME")
private String formdate;
#JsonProperty("REMARKS")
private String remarks;
public String getFormdate() {
return formdate;
}
public void setFormdate(String formdate) {
this.formdate = formdate;
}
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
this.remarks = remarks;
}
}
map.get("ROU_DATA") returns a List object, and the toString() method of List does not generate JSON text.
You don't need to convert back to a JSON text just to get the ROU[] created, just call convertValue(...).
String input = "{ \"password\":\"des123\",\"ROU_DATA\":[{\"FORM_RECEIVING_TIME\":\"12:00:00\",\"REMARKS\":\"Redemption of Unit\"}, {\"FORM_RECEIVING_TIME\":\"13:00:00\",\"REMARKS\":\"sALE of Unit\"}] }";
ObjectMapper mapper2 = new ObjectMapper();
Map<?, ?> json = mapper2.readValue(input, Map.class);
ROU[] pp1 = mapper2.convertValue(json.get("ROU_DATA"), ROU[].class);
for (ROU person : pp1)
System.out.println(person.getRemarks());
Output
Redemption of Unit
sALE of Unit
class A
public class ROU {
#JsonProperty("FORM_RECEIVING_TIME")
private String formdate;
#JsonProperty("REMARKS")
private String remarks;
public String getFormdate() {
return formdate;
}
public void setFormdate(String formdate) {
this.formdate = formdate;
}
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
this.remarks = remarks;
}
}
class B
public class ObjOuter {
private String password;
#JsonProperty("ROU_DATA")
private List<ROU> rous;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<ROU> getRous() {
return rous;
}
public void setRous(List<ROU> rous) {
this.rous = rous;
}
}
json to Object
ObjectMapper mapper = new ObjectMapper();
try {
ObjOuter outer = mapper.readValue(str, ObjOuter.class);
for (ROU rou : outer.getRous()) {
System.out.println(rou.getFormdate());
System.out.println(rou.getRemarks());
}
} catch (IOException e) {
e.printStackTrace();
}

Jackson read-only property in method

I need to exposes a property in my json that will be processed in the getter method.
The class:
public class Configuracao{
private String departamento;
public String getDepartamento(){/**getter code**/}
public void setDepartamento(String departamento){/**setter code**/}
public String getDepartamentos(){/***Some logic code***/}
}
The json that got in front: {departamento: "Lote", departamentos: "Lotes"}
Works fine in serialization, but when my front-end post the json back, jackson throws a unrecognized field exception caused by 'departamentos'. How can I tell that I just want to 'departamentos' be serialized by the method value and be ignored in deserialization. I tried #JsonIgnoreProperty, #JsonGetter and #JsonProperty(access = JsonProperty.Access.READ_ONLY) on the method but nothing works.
You can use JsonIgnoreProperties annotation:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.concurrent.ThreadLocalRandom;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
Configuracao c = new Configuracao();
c.setDepartamento("D1");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(c);
System.out.println(json);
System.out.println(mapper.readValue(json, Configuracao.class));
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
class Configuracao {
private String departamento;
public String getDepartamento() {
return departamento;
}
public void setDepartamento(String departamento) {
this.departamento = departamento;
}
public String getDepartamentos() {
return departamento + " " + ThreadLocalRandom.current().nextDouble();
}
#Override
public String toString() {
return "Configuracao{" +
"departamento='" + departamento + '\'' +
'}';
}
}
Above code prints:
{"departamento":"D1","departamentos":"D1 0.8600092703789755"}
Configuracao{departamento='D1'}
JsonProperty.Access.READ_ONLY should also works:
class Configuracao {
private String departamento;
public String getDepartamento() {
return departamento;
}
public void setDepartamento(String departamento) {
this.departamento = departamento;
}
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getDepartamentos() {
return departamento + " " + ThreadLocalRandom.current().nextDouble();
}
#Override
public String toString() {
return "Configuracao{" +
"departamento='" + departamento + '\'' +
'}';
}
}
with above test works as expected.
If you have more classes like this and fields to ignore, you can disable globally feature DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Everything was tested with version 2.9.9
Just define departamentos property in Configuracao class.
public class Configuracao{
private String departamento;
private String departamentos;
//omitted getter/setter
}

Jackson: Store nested json object as JSONObject

This question is duplicate of Link, Code is used as:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
Car car = mapper.readValue(new File("fileName"), Car.class);
But for settings as given in the link, value comes as {}. How to solve this issue? I want to use nested object as JSONObject.
First of all, you mean JsonObject that is standard from Java EE 7 and not JSONObject that is in Android.
Even in the link you provide they talk about it.
Than, here is my Car example:
package org.mas.examples.jackson.models;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.json.JsonObject;
public class Car {
#JsonProperty(value="id")
private long id;
#JsonProperty(value="name")
private String name;
#JsonIgnore
private JsonObject settings;
public void setId(long id) {
this.id = id;
}
public long getId() {
return this.id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setSettings(JsonObject settings) {
this.settings = settings;
}
public JsonObject getSettings() {
return settings;
}
#JsonAnySetter
public void setJsonProperty(String key, Object value) {
if ("settings".equals(key)) {
System.out.println("Got it " + key + " value: " + value);
/* JsonObject settings = ....; create here your JsonObject */
} else {
System.out.println(key + " value: " + value);
}
}
}
If you run a test like this:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
Car car = objectMapper.readValue(json, Car.class);
System.out.println("Load a car: " + car.getName());
} catch (IOException e) {
e.printStackTrace();
}
You're going to obtain the following result:
Got it settings value: {color=#ff0000}
Load a car: test
So it work just on settings as described in Jackson Annotation doc:
#JsonAnySetter: annotation used for defining a two-argument method as
"any setter", used for deserializing values of otherwise unmapped JSON
properties

Jackson Serialization for subclasses

In the below example, I have a primary class - A and its subclass - B. Both can be used as a property in the general class X.
public class A
{
#JsonProperty("primary_key")
public final String primaryKey;
#JsonCreator
A(#JsonProperty("primary_key") String primaryKey)
{
this.primaryKey = primaryKey;
}
}
public class B extends A
{
#JsonProperty("secondary_key")
public final String secondaryKey;
#JsonCreator
B(#JsonProperty("primary_key") String primaryKey, #JsonProperty("secondary_key") String secondaryKey)
{
super(primaryKey);
this.secondaryKey = secondaryKey;
}
}
public class X
{
#JsonProperty("keys")
public final A keys;
#JsonCreator
X(#JsonProperty("keys") A keys)
{
this.keys = keys;
}
}
How can I use Jackson Polymorphic feature in order to correctly deserialize the below given json into their respective classes:
JSON A :
{ "keys" :{
"primary_key" : "abc"
}
}
JSON B :
{ "keys" : {
"primary_key" : "abc",
"secondary_key" : "xyz"
}
}
Expected Result: Map keys object to Class A for JSON A and Class B for JSON B.
Please suggest alternative suggestions too.
It feels like a pretty common problem and there is no easy annotations way to solve it (Or maybe i just cant find one):
Jackson Polymorphic Deserialization - Can you require the existence of a field instead of a specific value?
Deserializing polymorphic types with Jackson
One thing you can do is to add custom deserializer to your object mapper. Here is nice demo of this approach: https://stackoverflow.com/a/19464580/1032167
Here is demo related to your example:
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
public class Main4 {
private static final String jsonA = "{ \"keys\" : { \"primary_key\" : \"abc\" } }";
private static final String jsonB =
"{ \"keys\" : { \"primary_key\" : \"abc\", \"secondary_key\" : \"xyz\" } }";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule idAsRefModule = new SimpleModule("ID-to-ref");
idAsRefModule.addDeserializer(A.class, new AJsonDeserializer());
mapper.registerModule(idAsRefModule);
X tl = mapper.readValue(jsonA, X.class);
System.out.println(tl);
X t2 = mapper.readValue(jsonB, X.class);
System.out.println(t2);
}
public static class AJsonDeserializer extends JsonDeserializer<A>{
#Override
public A deserialize(JsonParser jp, DeserializationContext dc)
throws IOException {
ObjectCodec codec = jp.getCodec();
JsonNode node = codec.readTree(jp);
if (node.has("secondary_key")) {
return codec.treeToValue(node, B.class);
}
return new A(node.findValue("primary_key").asText());
}
}
public static class A
{
#JsonProperty("primary_key")
public final String primaryKey;
#JsonCreator
A(#JsonProperty("primary_key") String primaryKey)
{
this.primaryKey = primaryKey;
}
#Override
public String toString() {
return "A{" +
"primaryKey='" + primaryKey + '\'' +
'}';
}
}
public static class B extends A
{
#JsonProperty("secondary_key")
public final String secondaryKey;
#JsonCreator
B(#JsonProperty("primary_key") String primaryKey,
#JsonProperty("secondary_key") String secondaryKey)
{
super(primaryKey);
this.secondaryKey = secondaryKey;
}
#Override
public String toString() {
return "B{" +
"primaryKey='" + primaryKey + '\'' +
"secondaryKey='" + secondaryKey + '\'' +
'}';
}
}
public static class X
{
#JsonProperty("keys")
public final A keys;
#JsonCreator
X(#JsonProperty("keys") A keys)
{
this.keys = keys;
}
#Override
public String toString() {
return "X{" +
"keys=" + keys +
'}';
}
}
}
But you will have to create one more super class if you want to use default A deserializer or look here how you can solve this: https://stackoverflow.com/a/18405958/1032167
If I understoon correctly, simply passing the values will work, without any config. I believe this is what you are looking for:
public class Test {
private static final String JSON = "{\"keys\":{\"primary_key\":\"abc\",\"secondary_key\":\"xyz\"}}";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
X x = mapper.readValue(JSON, X.class);
System.out.println(mapper.writeValueAsString(x));
}
}
class A {
private String primary_key;
public String getPrimary_key() {
return primary_key;
}
public void setPrimary_key(String primary_key) {
this.primary_key = primary_key;
}
}
class B extends A {
private String secondary_key;
public String getSecondary_key() {
return secondary_key;
}
public void setSecondary_key(String secondary_key) {
this.secondary_key = secondary_key;
}
}
class X {
private B keys;
public B getKeys() {
return keys;
}
public void setKeys(B keys) {
this.keys = keys;
}
}
Output will be:
{"keys":{"primary_key":"abc","secondary_key":"xyz"}}
In case this is not what you expect, please provide another explanation and I will edit the answer as needed.

JSON parsing through GSON returns null values

I am trying to read the values of a JSON output.
This is the JSON output:
{"nameOfSummoner":{"id":56529189,"name":"test","profileIconId":550,"summonerLevel":30,"revisionDate":1422110739000}}
And with the following code I am trying to read it:
final Connector connector = new Connector();
String response = connector.connect("link"); // (Returns a String value of the JSON)
final Gson gson = new Gson();
final Summoner summoner = gson.fromJson(response, Summoner.class); //Summoner is a model class
System.out.println(summoner);
Summoner class:
public class Summoner {
private String name;
private long profileIconId;
private long summonerLevel;
private long revisionDate;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public long getProfileIconId() {
return profileIconId;
}
public void setProfileIconId(final long profileIconId) {
this.profileIconId = profileIconId;
}
public long getSummonerLevel() {
return summonerLevel;
}
public void setSummonerLevel(final long summonerLevel) {
this.summonerLevel = summonerLevel;
}
public long getRevisionDate() {
return revisionDate;
}
public void setRevisionDate
(long revisionDate) {
this.revisionDate = revisionDate;
}
#Override
public String toString() {
return "Summoner{" +
"name='" + name + '\'' +
", profileIconId=" + profileIconId +
", summonerLevel=" + summonerLevel +
", revisionDate=" + revisionDate +
'}';
}
}
And I get the following output on the console:
Summoner{name='null', profileIconId=0, summonerLevel=0, revisionDate=0}
I have sadly no idea why this happens. Any help I get is appreciated. I am fairly sure it has to do with the JSON output that "nameOfSummoner" is on top and maybe that's why it does not read what is below.
As mentioned by #PeterMmm , your input is a map with 1 key-value pair.
You need to Create another POJO with Summoner object as attribute:
public class Sample {
private Summoner nameOfSummoner;
//getters and setters
}
and then try parsing. Or, you could create a Map and parse.
Map<String, Summoner> responseObj = new HashMap<String, Summoner>();
responseObj= gson.fromJson(response, responseObj.class);
Summoner obj = responseObj.get("nameOfSummoner");
You will also need to have "id" attribute in Summoner class I believe, else gson will throw an exception.

Categories