I have below classes:
public class Result<T> {
public int code;
public Object meta;
public T data;
}
public class User {
public int id;
public String name;
}
public class Error {
public String field;
public String message;
}
I want to deserialize a JSON payload based on code field. If code >= 10, return Result<ArrayList<Error>>, otherwise return Result<User>
Currently, I map JSON to Result<Object> first, then check the code field. Based on that value I make second map to desired object.
ObjectMapper mapper = new ObjectMapper();
Result<Object> tempResult = mapper.readValue(json, new TypeReference<Result<Object>>() {});
if (tempResult.code < 10) {
Result<User> result = mapper.readValue(json, new TypeReference<Result<User>>() {});
return result;
} else {
Result<ArrayList<Error>> result = mapper.readValue(json, new TypeReference<Result<ArrayList<Error>>>() {});
return result;
}
Is there an elegant way to do this without deserializing it 2 times?
You need to implement custom TypeIdResolver:
class UserTypeIdResolverBase extends TypeIdResolverBase {
#Override
public String idFromValue(Object value) {
throw new IllegalStateException("Not implemented!");
}
#Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
throw new IllegalStateException("Not implemented!");
}
#Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.CUSTOM;
}
#Override
public JavaType typeFromId(DatabindContext context, String id) {
if (Integer.parseInt(id) < 10) {
return context.getTypeFactory().constructType(new TypeReference<Result<User>>() {});
}
return context.getTypeFactory().constructType(new TypeReference<Result<List<Error>>>() {});
}
}
and declare it for a Result class:
#JsonTypeInfo(property = "code", use = JsonTypeInfo.Id.CUSTOM, visible = true)
#JsonTypeIdResolver(UserTypeIdResolverBase.class)
class Result<T>
Related
I want to convert a json into Java class by having custom deserializer.
I'm able to serialize ACC_NUM, NAME and any other fields from json but not sure what can be done to convert MOBILE_NUMBER_1,MOBILE_NUMBER_2 like fields into single JSONArray(See AccountInfo class). There can be many more fields like this and count also is not fixed. Example there can be ADDRESS_1, ADDRESS_2 till ADDRESS_20 and so on and all this fields should go in JSONArray of ADDRESS after deserilization.
I have a Map of Map which holds info like this:
{
"accountInfo": {
"ACC_NUM": "1234567890",
"NAME": "John Cena",
"MOBILE_NUMBER_1": "12376534",
"MOBILE_NUMBER_2": "12376534",
"MOBILE_NUMBER_3": "12376534",
"MOBILE_NUMBER_4": "12376534"
},
"someOther": {
//similer to above
}
}
This info I want to convert to the following class CommonInfo:
public class CommonInfo {
private AccountInfo accountInfo;
//other properties...
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class AccountInfo {
#JsonProperty("ACC_NUM")
private FieldValue<BigInteger> accountNum;
#JsonProperty("NAME")
private FieldValue<String> name;
#JsonProperty("MOBILE_NUMBER")
private FieldValue<JSONArray> mobileNumber;
}
//FieldValue class
public interface FieldValue<T> {
T getInitialValue();
void setInitialValue(T initialValue);
T getValue();
void setValue(T value);
}
#JsonInclude(JsonInclude.Include.ALWAYS)
public class FieldValueImpl<T> implements FieldValue<T> {
protected T initialValue;
protected T value;
//getters, setters, cons..
}
My service code takes json/Map and tries to convert it to CommonInfo class
#Service
public class MyService {
private final ObjectMapper jsonMapper = new ObjectMapper();
#PostConstruct
protected void init() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(FieldValue.class, new FieldValueSerializer());
simpleModule.addDeserializer(FieldValue.class, new FieldValueDeserializer());
jsonMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
jsonMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
jsonMapper.registerModule(simpleModule);
}
public CommonInfo setPojoResult(Map<String, LinkedHashMap<String, String>> contentAsMap) {
return jsonMapper.convertValue(contentAsMap, CommonInfo.class);
}
}
Serializer and Deserializer looks like this:
public class FieldValueDeserializer extends JsonDeserializer<FieldValue<?>> implements ContextualDeserializer {
private JavaType valueType;
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
var deserializer = new FieldValueDeserializer();
if (property == null) {
deserializer.valueType = ctxt.getContextualType().containedType(0);
} else {
var wrapperType = property.getType();
var valueType = wrapperType.containedType(0);
deserializer.valueType = valueType;
}
return deserializer;
}
#Override
public FieldValue<?> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
FieldValueDeserializer deserializer = new FieldValueDeserializer();
deserializer.getKnownPropertyNames();
FieldValue<?> fieldValueImpl = new FieldValueImpl<>();
if (valueType.toString().contains("java.time.LocalDate")) {
JsonNode node = parser.getCodec().readTree(parser);
FieldValue<LocalDate> f1 = new FieldValueImpl<>();
f1.setValue(DateUtils.convertJulianToLocalDate(node.textValue()));
return f1;
} else {
fieldValueImpl.setValue(context.readValue(parser, valueType));
}
return fieldValueImpl;
}
}
//--
public class FieldValueSerializer extends StdSerializer<FieldValue> {
public FieldValueSerializer() {
this(null);
}
public FieldValueSerializer(Class<FieldValue> vc) {
super(vc);
}
#Override
public void serialize(FieldValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(String.valueOf(value.getCurValue()));
}
}
I have base class WebAnswer,
public class WebAnswer<T> {
private int id;
private T result;
private ErrorModel error;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public ErrorModel getError() {
return error;
}
public void setError(ErrorModel error) {
this.error = error;
}
}
and inherited class RequestAnsModel
public class RequestAnsModel extends WebAnswer<RequestModel> {
}
I need to deserialize JSON object with Gson, in code I do it like this:
Gson gson = new Gson();
WebAnswer<RequestModel> data = new WebAnswer<RequestModel>();
data = gson.fromJson(response.toString(),data.getClass());
and in Field result I get array of objects com.google.gson.internal.LinkedTreeMap$Node#. But when I do something like this
WebAnswer<RequestModel> data = new RequestAnsModel();
I get correct model where result is object of RequestModel. Are there any ways to desirialize in gson somthing like this correct with Generic without create inherited class?
You should use TypeToken like in the code below:
Gson gson = new Gson();
Type type = new TypeToken<WebAnswer<RequestModel>>() {
}.getType()
WebAnswer<RequestModel> data = gson.fromJson(response.toString(), type);
I'm running into a few issues similar to what others have had in the past with Json parsing in Java. This is the first time I try something like this so any help/tips is extremely useful.
I'm trying to parse in data from this site: https://api.bitcoinaverage.com/exchanges/USD
I have tried numerous ways with both Json and Gson. And have tried looking for help here but to no avail.
Here are the classes that are set up (these were auto generated):
Info.java:
public class Info{
private String display_URL;
private String display_name;
private Rates[] rates;
private String source;
private Number volume_btc;
private Number volume_percent;
public String getDisplay_URL(){
return this.display_URL;
}
public void setDisplay_URL(String display_URL){
this.display_URL = display_URL;
}
public String getDisplay_name(){
return this.display_name;
}
public void setDisplay_name(String display_name){
this.display_name = display_name;
}
public Rates[] getRates(){
return this.rates;
}
public void setRates(Rates[] rates){
this.rates = rates;
}
public String getSource(){
return this.source;
}
public void setSource(String source){
this.source = source;
}
public Number getVolume_btc(){
return this.volume_btc;
}
public void setVolume_btc(Number volume_btc){
this.volume_btc = volume_btc;
}
public Number getVolume_percent(){
return this.volume_percent;
}
public void setVolume_percent(Number volume_percent){
this.volume_percent = volume_percent;
}
}
Rates.java:
public class Rates {
private Number ask;
private Number bid;
private Number last;
public Number getAsk(){
return this.ask;
}
public void setAsk(Number ask){
this.ask = ask;
}
public Number getBid(){
return this.bid;
}
public void setBid(Number bid){
this.bid = bid;
}
public Number getLast(){
return this.last;
}
public void setLast(Number last){
this.last = last;
}
}
MainClass.java:
public class MainClass {
public static void main(String[] args) throws Exception {
Gson gson = new Gson();
String json = readUrl("https://api.bitcoinaverage.com/exchanges/USD");
Info page = gson.fromJson(json, Info.class);
System.out.println(page.getDisplay_name());
}
private static String readUrl(String urlString) throws Exception {
BufferedReader reader = null;
try {
URL url = new URL(urlString);
reader = new BufferedReader(new InputStreamReader(url.openStream()));
StringBuffer buffer = new StringBuffer();
int read;
char[] chars = new char[1024];
while ((read = reader.read(chars)) != -1)
buffer.append(chars, 0, read);
return buffer.toString();
} finally {
if (reader != null)
reader.close();
}
}
}
When I try to call a getter, a null is returned.
How do I go about parsing the data properly, and then being able to call an attribute from which ever object I want? For example, if I want an attribute from "anx_hk" or "bitfinex".
This is the first time me posting something here so I hope I'm following the proper guidelines.
I also plan on passing this over to Android once I get the fell for parsing Json better. Thanks for the help! It'll greatly be appreciated.
I'll be honest with you, that's a pretty lame API response. Here it is
{
"anx_hk": {
"display_URL": "https://anxbtc.com/",
"display_name": "ANXBTC",
"rates": {
"ask": 454.26,
"bid": 444.46,
"last": 443.78
},
"source": "bitcoincharts",
"volume_btc": 11.73,
"volume_percent": 0.02
},
...,
"timestamp": "Fri, 04 Apr 2014 04:30:26 -0000",
...
}
There's no JSON array here, so you can get rid of all your array types. This response is a JSON object, which contains a bunch of JSON objects (which share a format) and a JSON name value pair where the name is timestamp.
The common JSON objects have two fields of type double (that's what type your field should be, not Number)
"volume_btc": 11.73,
"volume_percent": 0.02
, three fields of type String
"display_URL": "https://anxbtc.com/",
"display_name": "ANXBTC",
"source": "bitcoincharts",
and one that is a JSON object that contains three more doubles
"rates": {
"ask": 454.26,
"bid": 444.46,
"last": 443.78
}
The actual issue here is that, I'm assuming, the JSON objects in the root JSON object have names that may change or new ones may be added. This is not a good fit for a POJO. Instead you'd want to use a Map<String, Info>, but Gson can't map to that by default. It is not well suited for such deserialization. You'd have to provide your own TypeAdapter.
Instead, I'm going to suggest you use Jackson.
If we put that all together, we get something like
class ApiResponse {
private Map<String, Info> page = new HashMap<>();
private Date timestamp;
public Map<String, Info> getPage() {
return page;
}
#JsonAnySetter
public void setPage(String name, Info value) {
page.put(name, value);
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
}
class Info {
private String display_URL;
private String display_name;
private Rates rates;
private String source;
private Double volume_btc;
private Double volume_percent;
public String getDisplay_URL() {
return this.display_URL;
}
public void setDisplay_URL(String display_URL) {
this.display_URL = display_URL;
}
public String getDisplay_name() {
return this.display_name;
}
public void setDisplay_name(String display_name) {
this.display_name = display_name;
}
public Rates getRates() {
return this.rates;
}
public void setRates(Rates rates) {
this.rates = rates;
}
public String getSource() {
return this.source;
}
public void setSource(String source) {
this.source = source;
}
public Double getVolume_btc() {
return this.volume_btc;
}
public void setVolume_btc(Double volume_btc) {
this.volume_btc = volume_btc;
}
public Double getVolume_percent() {
return this.volume_percent;
}
public void setVolume_percent(Double volume_percent) {
this.volume_percent = volume_percent;
}
}
class Rates {
private Double ask;
private Double bid;
private Double last;
public Number getAsk() {
return this.ask;
}
public void setAsk(Double ask) {
this.ask = ask;
}
public Double getBid() {
return this.bid;
}
public void setBid(Double bid) {
this.bid = bid;
}
public Double getLast() {
return this.last;
}
public void setLast(Double last) {
this.last = last;
}
}
With deserialization code such as
String json = readUrl("https://api.bitcoinaverage.com/exchanges/USD");
ObjectMapper mapper = new ObjectMapper();
ApiResponse response = mapper.readValue(json, ApiResponse.class);
System.out.println(response);
With appropriate toString() methods (mine were auto-generated with Eclipse), you would get something like
ApiResponse [pages={bitkonan=Info [display_URL=https://bitkonan.com/, display_name=BitKonan, rates=Rates [ask=475.0, bid=438.01, last=437.0], source=api, volume_btc=7.24, volume_percent=0.01], vaultofsatoshi=Info [display_URL=https://vaultofsatoshi.com, display_name=Vault of Satoshi, rates=Rates [ask=460.0, bid=460.0, last=460.0], source=api, volume_btc=11.46, volume_percent=0.02], bitstamp=Info [display_URL=https://bitstamp.net/, display_name=Bitstamp, rates=Rates [ask=439.16, bid=436.34, last=436.34], source=api, volume_btc=22186.29, volume_percent=35.19], ...}, timestamp=Fri Apr 04 01:02:43 EDT 2014]
as output.
The api response contains many objects, but seems that you are trying to read them as a single Info object.
You may try to read the response as a Map<String, Info>, and iterate the entries.
Map<String, Info> hashMap = gson.fromJson(body, HashMap.class);
for (Map.Entry entry : hashMap.entrySet()) {
// your code
}
i have this string which i'm using for testing of api:
{"limit":30, "offset":"0", "filters": [{"property":"vlc.vlc","operator":"=","value":"DEKU113829"}]}
I would like to create JSOn object for processing in Android app using the:
JSONObject json = new JSONObject();
json.put("limit", 30);
json.put("offset", "0");
But i don't know how to create "filters" section using put method...
What is the right and most effective solution for this?
JSONObject/JSONArray supports a "builder-esque" pattern and put can be chained - it will return the same (but modified) object.
JSONObject json =
new JSONObject()
.put("limit", 30)
.put("offset", "0") /* but should be 0? */
.put("filters",
new JSONArray()
.put(new JSONObject()
.put("property", "vlc.vlc")
.put("operator", "=")
.put("value", "DEKU113829")
)
);
Alternatively, look into a POJO mapper like Gson, which I would recommend overall for ease of use and consistency.
Try this...
JSONObject json = new JSONObject();
json.put("limit", 30);
json.put("offset", "0");
JSONArray js_array = new JSONArray();
JSONObject json_obj = new JSONObject();
json_obj.put("property", "vlc.vlc");
json_obj.put("operator", "=");
json_obj.put("value", "DEKU113829");
js_array.put(json_obj);
json.put("filters",js_array);
Use the Gson Library. Your model objects for the JSON will be
public class MainModel
{
private int limit ;
public int getlimit()
{
return this.limit;
}
public void setlimit(int limit)
{
this.limit = limit;
}
private String offset ;
public String getoffset()
{
return this.offset;
}
public void setoffset(String offset)
{
this.offset = offset;
}
private ArrayList<Filter> filters ;
public ArrayList<Filter> getfilters()
{
return this.filters;
}
public void setfilters(ArrayList<Filter> filters)
{
this.filters = filters;
}
}
public class Filter
{
private String property ;
public String getproperty()
{
return this.property;
}
public void setproperty(String property)
{
this.property = property;
}
private String operator ;
public String getoperator()
{
return this.operator;
}
public void setoperator(String operator)
{
this.operator = operator;
}
private String value ;
public String getvalue()
{
return this.value;
}
public void setvalue(String value)
{
this.value = value;
}
}
You can populate your filter object and create MainModel object and add the filter object to it. Next use Gson library as below to get your json string
Gson gsonParser = new Gson();
String jsonString = gsonParser.toJson(mainModelObject);
I have a class DocumentBO which has the following attributes -
public class DocumentBO implements IStorageBO {
private String aId;
private String studyId;
private Map<AlgorithmsEnum, JobIOStatus> status;
private String text;
private Collection<Sentence> sentences;
public String getaId() {
return aId;
}
public void setaId(String aId) {
this.aId = aId;
}
public String getStudyId() {
return studyId;
}
public void setStudyId(String studyId) {
this.studyId = studyId;
}
public Map<AlgorithmsEnum, JobIOStatus> getStatus() {
return status;
}
public void setStatus(Map<AlgorithmsEnum, JobIOStatus> status) {
this.status = status;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Collection<Sentence> getSentences() {
return sentences;
}
public void setSentences(Collection<Sentence> sentences) {
this.sentences = sentences;
}
}
The AlgorithmsEnum is as follows -
public enum AlgorithmsEnum {
SENTIMENT("sentiment"),
INTENTION("intention"),
TOPIC("topic"),
NER("ner"),
UIMA("uima");
private final String value;
private AlgorithmsEnum(String value) {
this.value = value;
}
public String value() {
return value;
}
#Override
public String toString() {
return value;
}
public static AlgorithmsEnum fromValue(String value) {
if (value != null) {
for (AlgorithmsEnum aEnum : AlgorithmsEnum.values()) {
if (aEnum.value().equals(value)) {
return aEnum;
}
}
}
return null;
}
}
The JobIOStatus is also similar.
I am successfully able to create a JSON string of Collection using GSON using the following TypeToken
Type type = new TypeToken<Collection<DocumentBO>>() {}.getType();
But, when I try to recreate the Collection object using the JSON string returned by Gson and the same TypeToken, the key of the status hashmap is always returned as NULL whereas the value is successfully created. What do you think can be the issue?
The problem is that you have overridden toString() in your enum.
If you look at the JSON being produced, the keys to your Map<AlgorithmsEnum, JobIOStatus> are the lowercase names you're creating. That won't work. Gson has no idea how to recreate the enum from those when you attempt to deserialize the JSON.
If you remove your toString() method it will work just fine.
Alternatively you can use the .enableComplexMapKeySerialization() method in GsonBuilder when serializing which will ignore your toString() method and produce JSON using the default representations of your enum values which is what is required.
There are "well" known :) issues of Gson to serialize Map when the key is derived from object and its not a "native" data type.
Please use this
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.enableComplexMapKeySerialization().create();
Collection<DocumentBO> obj = gson.fromJson(str, type);