I actually have some web-services make with restfullyii in PHP.
But I have some troubles to deserialize response of my web-services with jackson.
This is an example of response :
{"success":true,"message":"Record(s) Found","data":{"totalCount":"1","user":{...}}}
To deserialize this response I make this model :
#JsonIgnoreProperties(ignoreUnknown = true)
public class response {
#JsonProperty("data")
private HashMap<String, Object> data;
#JsonProperty("message")
private String message;
#JsonProperty("success")
private Boolean success;
public HashMap<String, Object> getData() {
return data;
}
public void setData(HashMap<String, Object> data) {
this.data = data;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
}
And to deserialize user I use these lines :
(rst is a result of deserialize response)
ObjectMapper mapper = new ObjectMapper();
try {
String rstTxt = String.valueOf(rst.getData().get("user"));
System.out.println(rstTxt);
user user = mapper.readValue(rstTxt, user.class);
} catch (JsonParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JsonMappingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
But it doesn't work because "rst.getData().get("user")" returns a string in this schema :
{ attribute = value }
Indeed, the following exception is returned :
org.codehaus.jackson.JsonParseException: Unexpected character ('i' (code 105)): was expecting double-quote to start field name
Have you an idea about how I could do to deserialize user attribute ?
Thank you.
I'm quite guessing but maybe, since you've defined a Map<String, Object>, your User should be already deserialized into an Object, and maybe you should just try to cast it:
User user = (User) rst.getData().get("user");
otherwise you can modify slightly your code to match exactly the response, like:
public class Response {
private String message;
private Boolean success;
private Data data;
}
and
public class Data {
private String totalCount;
private User user;
}
In this way you should get everything immeadiately deserialized.
Tip: if your variable names are the same you don't need the #JsonProperty annotation!
Related
I'm getting the error below when I try to POST or PUT to my rest resource. The GET and DELETE (for individual Starlinks) requests works just fine FWIW. I have other resources which basically follow the same pattern of classes—some with entities with EmbeddedIds and some without, and all their REST methods work properly. The only difference is that in this instance, I introduced an entity relationship (#ManyToOne) between my StarLink class and a Star class which allowed me to access my StarLinks from a Star resource through HATEOAS—but that seems to have thrown a wrench in things. Tried looking for solutions but, I'm beat.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [com.beezassistant.configurator.models.StarLinkId]
...
Here are the relevant classes:
StarLinkRepository.java
#RepositoryRestResource(exported=true, path="starlinks")
public interface StarLinkRepository extends JpaRepository<StarLink, StarLinkId> {}
StarLink.java
#Entity
#Table(name="starlink")
public class StarLink implements Serializable {
// ...
#EmbeddedId
private StarLinkId starLinkId;
#ManyToOne
#MapsId("starName")
private Star star;
private String linkName;
public StarLink() {
super();
starLinkId = new StarLinkId();
}
// Getters and setters
}
StarLinkId
#Embeddable
public class StarLinkId implements Serializable {
// ...
private String starName;
private String link;
public StarLinkId() {
super();
}
// Getters and setters
// equals and hashCode
}
StarLinkIdConverter
#Component
public class StarLinkIdConverter implements BackendIdConverter {
#Override
public boolean supports(Class<?> delimiter) {
return StarLink.class.equals(delimiter);
}
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
String[] parts = id.split("__");
StarLinkId starLinkId = new StarLinkId();
starLinkId.setStarName(parts[0]);
try {
starLinkId.setLink(
URLDecoder.decode(
parts[1],
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
starLinkId = null;
}
return starLinkId;
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
StarLinkId starLinkId = (StarLinkId) id;
try {
return String.format(
"%s__%s",
starLinkId.getStarName(),
URLEncoder.encode(
starLinkId.getLink(),
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
return null;
}
}
}
I had the same issue and I found a related issue here.
Implementing a BackendIdConverter will not work as it's for Spring WebMVC controllers.
You need to implement a Converter<String, StarLinkId>, as follows:
public class StarLinkIdConverter implements Converter<String, StarLinkId> {
#Override
public StarLinkId convert(String source) {
String[] parts = source.split("__");
StarLinkId starLinkId = new StarLinkId();
starLinkId.setStarName(parts[0]);
try {
starLinkId.setLink(
URLDecoder.decode(
parts[1],
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
starLinkId = null;
}
return starLinkId;
}
}
Then you have to register this converter in your configuration, in your class where you extend RepositoryRestConfigurer, as follows:
#Override
protected void configureConversionService(ConfigurableConversionService conversionService) {
super.configureConversionService(conversionService);
conversionService.addConverter(new StarLinkIdConverter());
}
This will give you the conversion from the request URI to your StarLinkId, and this will make requests work as long as you assemble the request URI yourself, but to correctly do HATEOAS you still need to make sure you return the correctly formatted URIs, by overriding toString in your StarLinkId class:
#Override
public String toString() {
try {
return String.format(
"%s__%s",
getStarName(),
URLEncoder.encode(
getLink(),
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
return null;
}
}
I haven't tested the above code, but these steps fixed this issue for me.
I am coding in an Spring Boot Project and there was a lot of API with diffrent Request Param so I'm trying to write a generic function with mapper an request param into a list object, then cast it into a class like the code below
public static <D> List<D> convertStringListToObject(String string) {
if (string == null) return null;
try {
return objectMapper.readValue(string, new TypeReference<>() {
});
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
But the result is it can only return a list of Object not the list of D class like I'm expected. Does anyone have any ideas how to write this function?
Eddited:
Here is how I invoke it:
filterBlockRequestDto.setPopularFiltersList(ApiUtil.convertStringListToObject(filterBlockRequestDto.getPopularFilters()));
The FilterBlockRequestDto class
import lombok.*;
import java.util.List;
#Getter
#Setter
#Builder
#ToString
#AllArgsConstructor
#NoArgsConstructor
public class FilterBlockRequestDto {
Integer locationId;
Integer projectId;
String totalBudget;
List<TotalBudgetDto> totalBudgetList;
// The string was pass in Request param
String popularFilters;
List<PopularFiltersDto> popularFiltersList;
Integer viewRating;
Integer numberOfBed;
}
One way is to accept type reference as parameter so that the caller can provide the target class and as TypeReference is a subclass, generic type information will be available at runtime.
public static <D> List<D> convertStringListToObject(String string, TypeReference<List<D>> typeReference) {
if (string == null) return null;
try {
return objectMapper.readValue(string, typeReference);
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
You'll have to pass the type you want to deserialize your string to as well..
My approach on this would be something like this:
public static <T> T convertStringListToObject(String string, Class<T> clazz) {
if (string == null) {
return null;
}
try {
return objectMapper.readValue(string, clazz);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
and then use this method as follows:
List<Model> models =
Arrays.asList(Mapper.convertStringListToObject(stringList, Model[].class));
I have a number of json objects concatenated into one string, and need to parse all of them. Simple example:
String jsonStr = "{"name":"peter","age":40}{"name":"laura","age":50}"
When using an ObjectMapper of jackson to parse this, it finds and reads the first json correctly, and drops the rest of the string.
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonStr);
System.out.println(rootNode);
Gives output {"name":"peter","age":20}
Is there any way (in Jackson or another framework) of e.g. returning the number of read characters, or the rest of the string, or an array of JsonNodes?
I found questions with the same goal in JavaScript and in Python, where it was recommended to split by }{ or regex to reformat this to a json array, but I still hope for a more elegant solution.
You don't need to modify your input as suggested by others, just use below code.
Main Method
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonFactory factory = new JsonFactory(mapper);
JsonParser parser = factory.createParser(new File("config.json"));
// factory.createParser(String) and many other overload methods
// available, byte[], char[], InputStream etc.
Iterator<Person> persons = parser.readValuesAs(Person.class);
while(persons.hasNext()) {
Person p = persons.next();
System.out.printf("%s: %d%n", p.getName(), p.getAge());
}
}
Person Class
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
config.json file
{"name":"peter","age":40}{"name":"laura","age":50}
Program Output
peter: 40
laura: 50
Library used
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
In Json, an object structure starts with { and ends with }. Hence ObjectMapper thinks that there is nothing more to process as soon as it encounters }.
In Json an array is indicated with []. So if you wish to have multiple elements / objects it needs to be enclosed with [] and a comma separating individual objects
"[
{"name":"peter","age":40},
{"name":"laura","age":50}
]"
You can wrap the input with brackets [] and then replace every }{ with },{, and then finally parse the string as array:
String input = "{\"name\":\"peter\",\"age\":40}{\"name\":\"laura\",\"age\":50}"
String jsonArray = "[" + input.replace("}{", "},{") + "]";
ObjectMapper mapper = new ObjectMapper();
JsonNode parsedJsonArray = mapper.readTree(jsonArray);
you have to modify your JSON
String jsonStr = "[{\"name\":\"peter\",\"age\":40},{\"name\":\"laura\",\"age\":50}]";
There are multiple ways to convert JSON into java object.
1st Way
public JsonNode convertIntoJsonNode(String jsonStr)
{
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = null;
try {
rootNode = objectMapper.readTree(jsonStr);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return rootNode;
}
2nd Ways
// check also imports
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
// User Model
class User{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
#Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
//Coversion Method
public List<User> convertIntoObject(String jsonStr)
{
ObjectMapper objectMapper = new ObjectMapper();
User[] myObjects = null;
try {
myObjects = objectMapper.readValue(jsonStr, User[].class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return Arrays.asList(myObjects);
}
3rd way you can directly parse without array
public List<User> convertIntothirdway(String jsonStr)
{
ObjectMapper objectMapper = new ObjectMapper();
List<User> userList = null;
try {
userList = objectMapper.readValue(jsonStr, new TypeReference<List<User>>(){});
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return userList;
}
4th way
List<User> userList = mapper.readValue(jsonInput, mapper.getTypeFactory().constructCollectionType(List.class, User.class));
I am creating java app which will allow storing objects in database. What I want to do is generic implementation so it could load json and create java class from it. This is what a code should look like:
SomeClass someObject= data.getValue(SomeClass.class);
Lets say that data would be a json object. How should I implement getValue() method so it will allow me to create class from it. I don't want SomeClass to extend anything other then Object. I think that this should be done using generic classes but so far I have not worked with generic classes like this. Can you please point to a best way on how to acomplish this? Example code would be best.
Many thanks
You can consult the source code of Jackson library and look inside (or debug) the method BeanDeserializer#vanillaDeserialize(), there you'll find the loop which traverse through all json tokens, finds the corresponding fields and sets their values.
As a proof of concept, I've extracted part of the logic from Jacskson and wrapped it inside a naive (and fragile) object mapper and a naive (and fragile) json parser:
public static class NaiveObjectMapper {
private Map<String, Object> fieldsAndMethods;
private NaiveJsonParser parser;
public <T> T readValue(String content, Class<T> valueType) {
parser = new NaiveJsonParser(content);
try {
// aggregate all value type fields and methods inside a map
fieldsAndMethods = new HashMap<>();
for (Field field : valueType.getDeclaredFields()) {
fieldsAndMethods.put(field.getName(), field);
}
for (Method method : valueType.getMethods()) {
fieldsAndMethods.put(method.getName(), method);
}
// create an instance of value type by calling its default constructor
Constructor<T> constructor = valueType.getConstructor();
Object bean = constructor.newInstance(new Object[0]);
// loop through all json nodes
String propName;
while ((propName = parser.nextFieldName()) != null) {
// find the corresponding field
Field prop = (Field) fieldsAndMethods.get(propName);
// get and set field value
deserializeAndSet(prop, bean);
}
return (T) bean;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private void deserializeAndSet(Field prop, Object bean) {
Class<?> propType = prop.getType();
Method setter = (Method) fieldsAndMethods.get(getFieldSetterName(prop));
try {
if (propType.isPrimitive()) {
if (propType.getName().equals("int")) {
setter.invoke(bean, parser.getIntValue());
}
} else if (propType == String.class) {
setter.invoke(bean, parser.getTextValue());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private String getFieldSetterName(Field prop) {
String propName = prop.getName();
return "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
}
}
class NaiveJsonParser {
String[] nodes;
int currentNodeIdx = -1;
String currentProperty;
String currentValueStr;
public NaiveJsonParser(String content) {
// split the content into 'property:value' nodes
nodes = content.replaceAll("[{}]", "").split(",");
}
public String nextFieldName() {
if ((++currentNodeIdx) >= nodes.length) {
return null;
}
String[] propertyAndValue = nodes[currentNodeIdx].split(":");
currentProperty = propertyAndValue[0].replace("\"", "").trim();
currentValueStr = propertyAndValue[1].replace("\"", "").trim();
return currentProperty;
}
public String getTextValue() {
return String.valueOf(currentValueStr);
}
public int getIntValue() {
return Integer.valueOf(currentValueStr).intValue();
}
}
public static class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "id = " + id + ", name = \"" + name + "\"";
}
}
To see the deserialization in action run:
String json = "{\"id\":1, \"name\":\"jsmith\"}";
NaiveObjectMapper objectMapper = new NaiveObjectMapper();
User user = objectMapper.readValue(json, User.class);
System.out.println(user);
Or try online.
However I recommend not to reinvent the wheel and use Jackson and in case you need some custom actions you can use custom deserialization, see here and here.
I have JSON request and response, I want to print the JSONs in the log, but there are some secured fields which I want to avoid to print in the log, I am trying to mask fields keys:
example:
before masking:
{"username":"user1","password":"123456","country":"US","creditCardNumber":"1283-1238-0458-3458"}
after masking
{"username":"user1","password":"XXXXXX","country":"US","creditCardNumber":"XXXXXX"}
I am using java Gson lib, please help me to do that
EDIT
I want to pass the keys dynamically, so in function a I want to mask these fields, but in function b different fields.
I think you should exclude that fields from log. Below is a simple example using Gson and #Expose annotation.
public static void main(String[] args) throws IOException {
String json = "{\"username\":\"user1\",\"password\":\"123456\",\"country\":\"US\",\"creditCardNumber\":\"1283-1238-0458-3458\"}";
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
System.out.println(gson.toJson(user));
Gson gsonExpose = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
System.out.println(gsonExpose.toJson(user));
}
public class User {
#Expose
private String username;
private String password;
#Expose
private String country;
private String creditCardNumber;
}
Output will be:
{"username":"user1","password":"123456","country":"US","creditCardNumber":"1283-1238-0458-3458"}
{"username":"user1","country":"US"}
Another solution using Reflection:
public static void main(String[] args) throws IOException {
String json = "{\"username\":\"user1\",\"password\":\"123456\",\"country\":\"US\",\"creditCardNumber\":\"1283-1238-0458-3458\"}";
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
List<String> fieldNames = Arrays.asList("password", "creditCardNumber");
System.out.println(mask(user, fieldNames, "XXXXXXX"));
}
public static String mask(Object object, List<String> fieldNames, String mask) {
Field[] fields = object.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (fieldNames.contains(fields[i].getName())) {
try {
fields[i].setAccessible(true);
if (fields[i].get(object) != null) {
fields[i].set(object, mask);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Gson gson = new Gson();
return gson.toJson(object);
}
I like the above solution to mask using reflection but wanted to extend same for other field types and saving masked field to unmask again.
Create annotation #MaskedField on top of field.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface MaskedField {
}
public <T> Map<String,? super Object> maskObjectFields(T object){
Map<String,? super Object> values = new HashMap<>();
Arrays.stream(object.getClass().getDeclaredFields()).filter(field->null != field.getAnnotation(MaskedField.class)).
forEach(annotatedField->{
try {
if(annotatedField.getType().isAssignableFrom(String.class)) {
annotatedField.setAccessible(true);
values.put(annotatedField.getName(),annotatedField.get(object));
annotatedField.set(object, maskString((String) annotatedField.get(object)));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
return values;
}
public <T> void unMaskObjectFields(T object,Map values){
Arrays.stream(object.getClass().getDeclaredFields()).filter(field->null != field.getAnnotation(MaskedField.class)).
forEach(annotatedField->{
try {
annotatedField.setAccessible(true);
annotatedField.set(object,values.get(annotatedField.getName()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
}
private String maskString(String value){
if(Objects.isNull(value)) return null;
return null; //TODO: your logic goes here for masking
}