Handling boolean values and empty strings for a property in Jackson? - java

I have a JSON property that can be one of
{ "observed": true }
{ "observed": false }
{ "observed": "" }
I'd like to map it so that in Java it will one of "true", "false" or ""
#JsonProperty("observed")
private String observedValue;
Then I'll just make a getter that would give me a
public Optional<Boolean> getObservedOpt() {
if ("".equals(observedValue)) {
return Optional.empty();
} else {
return Optional.of(Boolean.parseBoolean(observedValue));
}
}
However, I am not sure how to make it converted true and false into strings. Or perhaps there's a more elegant way of doing it without the string comparison.

I would suggest configure object mapper with this feature ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, so in case of empty string it will be assigned to null
ObjectMapper mapper = new ObjectMapper()
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
And you can happily declare this field as Boolean type, be aware in case of empty string this field value will be null
#JsonProperty("observed")
private Boolean observedValue;

One solution could be using Custom JsonDeserializer
public static class StringBooleanDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser parser, DeserializationContext context) throws IOException {
if(parser.getValueAsBoolean()){
return "true";
}else{
if(parser.getTextLength()==0) {
return parser.getText();
}else{
return "false";
}
}
}
}
#JsonProperty("observed")
#JsonDeserialize(using=StringBooleanDeserializer.class)
private String observedValue;
Similarly, you can also write custom JSON Serializer.

Related

How to convert String variable of POJO as Integer while converting to Json using ObjectMapper?

I have Java class which has String variable member
String sampleNumber="1234";
When I convert that Pojo to JSON using ObjectMapper I get:
{
"sampleNumber":"1234"
}
But I need an integer type in JSON as follow
{
"sampleNumber":1234
}
Can any one help me to get the expected results with using ObjectMapper?
Change property to int
You can change your property to int:
class Pojo {
private int sampleNumber = 1234;
// getters, setters
}
and you will get:
{
"sampleNumber" : 1234
}
Add new getter with right type
If you do not want to change property type to int/Integer you need to ignore getter which returns String and add new getter which will return int/Integer:
class Pojo {
private String sampleNumber = "1234";
public Integer getSampleNumber() {
return Integer.parseInt(sampleNumber);
}
#JsonIgnore
public String getSampleNumberString() {
return sampleNumber;
}
public void setSampleNumber(String sampleNumber) {
this.sampleNumber = sampleNumber;
}
}
For above POJO you will get below JSON:
{
"sampleNumber" : 1234
}
Write custom serialiser which force int
You can write custom serialiser which tries to parse String and write it as Number:
class ForceIntSerializer extends StdScalarSerializer<Object> {
public ForceIntSerializer() {
super(Object.class);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value instanceof String) {
try {
gen.writeNumber(Integer.parseInt(value.toString()));
} catch (NumberFormatException e) {
gen.writeString(value.toString());
}
} else if (value instanceof Integer) {
gen.writeNumber(((Integer) value));
}
}
}
You can use it as below:
class Pojo {
private String sampleNumber = "1234";
#JsonSerialize(using = ForceIntSerializer.class)
public String getSampleNumber() {
return sampleNumber;
}
}
and you will get below JSON:
{
"sampleNumber" : 1234
}

Checking if received JSON has necessary values in Java

I have an API endpoint where I am receiving JSON files, and I am creating an object from the files. I also have another pre-existing object, and I am trying to check if certain values in the received JSON match certain values in my existing object. If the fields match I will continue to process the file further, if not I will scrap it. My thoughts so far is just to have if statements checking each value, but is there a better way to do this? Or are if statements ok?
Very quick code example of what I mean by just using if statements.
public boolean compareObjects(recievedObject, existingObject) {
if( !(recievedObject.getName().equals(existingObject.getName()))) {
//true
} else if( !(recievedObject.getLocation().equals(existingObject.getLocation())) ) {
return false;
}
// else if ... etc
return true;
}
Note that I am not trying to check if the received file has all the required JSON fields, just that a few particular fields have certain values.
Edit:
The JSON will be a very flat structure e.g
{
"name": "name",
"location": "location",
...
}
So my object will be pretty basic
public class recievedObject {
String location;
String name;
public String getLocation() {
return location;
}
public String getName() {
return name;
}
}
What you can do to avoid lot of it-else statements is to create some validation abstraction.
interface Validator<A, B> {
boolean validate(A receivedObject, B existingObject);
}
Then for each if create new implementation of Validator.
class NameValidator implements Validator<Expeceted, Received> {
#Override
public boolean validate(Expeceted receivedObject, Received existingObject) {
return existingObject.getName().equals(receivedObject.getName());
}
}
class LocationValidator implements Validator<Expeceted, Received> {
#Override
public boolean validate(Expeceted receivedObject, Received existingObject) {
return existingObject.getLocation().equals(receivedObject.getLocation());
}
}
You can create list of such a validators
List<Validator<Expeceted, Received>> validators = Arrays.asList(
new NameValidator(),
new LocationValidator()
);
And finally your compare method could simply iterate through all validators.
public boolean compareObjects(Received recievedObject, Expeceted expecetedObject) {
for (Validator<Expeceted, Received> validation : validators) {
if (! validation.validate(expecetedObject, recievedObject)) {
return false;
}
}
return true;
}
This way you can simply add new validators later and keep compare method untouched.
define a method similar to 'equal' for your class and at the endpoint check the existingObject.check(receivedObject), add import java.util.Objects to your class
public boolean check(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RecievedObject receivedObject=(RecievedObject) o;
//add based on the logic you want
return Objects.equals(location, receivedObject.location) &&
Objects.equals(name, receivedObject.name);
}

Jackson: Override primitive type deserialization?

We need to process some broken JSON from a legacy server here that wrongly encodes null values as literal "null" strings in its output.
I already found that I probably want to override https://github.com/FasterXML/jackson-core/blob/master/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java#L368 to "fix" this, but this seems to be so deep inside Jackson that I'd rather do it differently. Are there alternatives, for example by using the ObjectMapper to add a custom deserializer for the String.class or am I lost?
Ok, it worked by overriding the standard String deserializer. Unfortunately I had to copy the complete implementation over because org/codehaus/jackson/map/deser/std/StringDeserializer.java is final and cannot be extended.
public class FixesModule extends SimpleModule {
public FixesModule() {
super();
addDeserializer(String.class, new CustomStringDeserializer());
}
}
and
public class CustomStringDeserializer extends StdScalarDeserializer<String> {
private static final String NULL_STRING = "null";
public CustomStringDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonToken curr = jp.getCurrentToken();
// Usually should just get string value:
if (curr == JsonToken.VALUE_STRING) {
// BEGIN NULL_STRING fix
if (NULL_STRING.equals(jp.getText())) {
return null;
}
// END NULL_STRING fix
return jp.getText();
}
// [JACKSON-330]: need to gracefully handle byte[] data, as base64
if (curr == JsonToken.VALUE_EMBEDDED_OBJECT) {
Object ob = jp.getEmbeddedObject();
if (ob == null) {
return null;
}
if (ob instanceof byte[]) {
return Base64Variants.getDefaultVariant().encode((byte[]) ob, false);
}
// otherwise, try conversion using toString()...
return ob.toString();
}
// Can deserialize any scalar value, but not markers
if (curr.isScalarValue()) {
return jp.getText();
}
throw ctxt.mappingException(_valueClass, curr);
}
// 1.6: since we can never have type info ("natural type"; String, Boolean,
// Integer, Double):
// (is it an error to even call this version?)
#Override
public String deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException {
return deserialize(jp, ctxt);
}
}

jackson-mapper version 1.9.12 EnumDeserializer issue

i am trying to map String to enum Object using Jackson ObjectMapper.readValue(String,Class) API, problem is Lets SAY my json string contains a Task Object with Action enum as below
public enum Action {
#XmlEnumValue("Add")
ADD("Add"),
#XmlEnumValue("Amend")
AMEND("Amend"),
#XmlEnumValue("Delete")
DELETE("Delete"),
#XmlEnumValue("Pending")
PENDING("Pending");
private final String value;
Action(String v) {
value = v;
}
public String value() {
return value;
}
public static Action fromValue(String v) {
for (Action c: Action.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
and the jason string will be like this "{"action":"Add"}" then ObjectMapper.readValue(jsonString, Task.Class) throws
org.codehaus.jackson.map.deser.StdDeserializationContext.weirdStringException(StdDeserializationContext.java:243) for Action Add because it cant convert this Enum.
I tried adding custom Desiserializer, But EnumDeserializer getting called anyway. any ideas?
All objects are JAXB generated, so annotations not possible.
Thanks for the help
Have you tried:
new ObjectMapper().setAnnotationIntrospector(new JaxbAnnotationIntrospector()).readValue()

Trim all strings elements in a complex object

Can I write a generic method to trim all strings within an complex object (object containing other objects)? Should java reflection api be used to achieve this?Thanks.
I have provided a sample below. However in reality there could be multiple objects within objects. Each object might contain a collection of String or collection of other objects which may contain String. Is there a way to trim the Strings - ones directly with the objects and ones within collection.
public class School{
private List<Course> courses;
private List<Student> students;
// Getters and Setters
}
public class Course{
private String name;
private String xxx;
private String yyy;
private List<String> zzzList;
}
public class Student{
private Map<String,String> xxx;
private List<Course> courseList;
}
Yes, reflection is the way. Basically, you need to:
get the class of the top level object (with [object].getClass())
get all the fields of the object (with clazz.getFields() - beware, it works only with public fields)
check if the field is String (either get field.getType() and check it's a string, or do a field.get(the object) and a instanceof String)
if it's the case, replace the string in the object with the trimmed one, using field.set([your object],[trimmed string])
if the field is an object but not a string, call your method recursively
That will do the trick.
---- just seen your update
Trimming strings in collection will be more tricky, since the strings are not exposed as public fields of the collection (List for example).
You will need something more clever, that will check if an object is an instance of List, or Map, or etc... (or a derived class!).
Main problem is also that java generics are done with erasing type at compile type. So you cannot know that your field is List[String] or List[Integer] or whatever. Every List[?] becomes List.
Still you can try to do it like that:
if field type is List
iterate through the list values
if a value is instanceof String, you have to remove it from the list and insert in place the trimmed version
if a value is an object, there you go again recursively with your method.
Not very interesting in real life samples, but more on a library side maybe.
Long way to go though!
Yes, you can do that with reflection, quite easily. Just check if the field is instanceof String.
The exact way to do it depends on your object structure.
/*********************************************************************************************
* Trim first level children of string type in this object
* #param obj which all string properties to be trimmed
*********************************************************************************************/
public static void trimAll(final Object obj)
throws LocalException
{
if (obj==null) return;
final Class c = obj.getClass();
final Method[] methods = c.getMethods();
final Class[] SETTER_ARGS = new Class[]{String.class};
final Object[] SETTER_VAL = new Object[1];
final String SET = "set";
final String GET = "get";
final String SPACE = "\u0020";
final String TAB = "\t";
for (final Method m:methods)
{
try
{
final String name=m.getName();
if (
name.length()>GET.length()
&& name.indexOf(GET)==0
&& m.getReturnType().equals(String.class)
&& m.getParameterTypes().length==0)
{
final String v = (String)m.invoke(obj);
if (v!=null && (v.contains(SPACE) || v.contains(TAB)) )
{
final Method setter=c.getMethod(SET+name.substring(3),SETTER_ARGS);
if (setter!=null)
{
SETTER_VAL[0]=v.trim();
setter.invoke(obj,SETTER_VAL);
}
}
}
}
catch (final Throwable e)
{
throw new LocalException(LocalException.EC_GENERAL_EXCEPTION,e);
}
}
}
We can also use Jackson to serialize and then deserialize the object. While deserializing we can use custom deserializer to trim all the String values.
Create a deserializer like this:
public class TrimStringToNullDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
String value = jsonParser.getValueAsString();
if (isNull(value)) {
return null;
}
value = value.trim();
if (value.length() == 0) {
value = null;
}
return value;
}
And then we can use Jackson to trim all values:
public class TrimStringToNullConfiguration {
private ObjectMapper objectMapper;
public Client trimToNull(Client inputClient) throws JsonProcessingException {
return getObjectMapper().readValue(getObjectMapper().writeValueAsString(inputClient), Client.class);
}
private ObjectMapper getObjectMapper() {
if (isNull(objectMapper)) {
objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new TrimStringToNullDeserializer());
objectMapper.registerModule(module);
}
return objectMapper;
}
I have placed a working example over here.
private <T> T toTrim(T t) {
Field[] fields = t.getClass().getFields();
for (Field field : fields) {
try {
if (field.get(t) instanceof String) {
Object o = field.get(t);
String s = (String) o;
field.set(t, s.trim().toUpperCase());
}
} catch (IllegalAccessException e) {
log.info("Error converting field "+ field.getName() );
}
}
return t;
}
if (yourObject instanceof String){
yourObject = yourObject.trim();
}
Hope it helps :)

Categories