ChrisClark
ChrisClark

Reputation: 1

Serializing Java boolean array as 0 & 1s without a custom Serializer

I have a ConcurrentHashMap<Long, boolean[]> that I want to send to a client app. To optimize the size, I'd like to use 0 and 1 for false and true, respectively.

I have an ObjectMapper that I instantiate so I have tried this:

objectMapper.configOverride( boolean.class ).setFormat( JsonFormat.Value.forShape( Shape.NUMBER_INT ) ) ;

That did not make it produce 0 and 1s.

Can this be done using only configuration or will I have to create a custom Serializer? Thanks

Upvotes: 0

Views: 666

Answers (1)

hfontanez
hfontanez

Reputation: 6168

Using this class to test:

public class Sample {
    
    public Sample(String name, boolean flag) {
        this.name = name;
        this.flag = flag;
    }

    private String name;
    private boolean flag;
        
    public boolean getFlag() {
        return flag;
    }
    
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

And trying the OP's approach to convert a Boolean value into a number

ObjectMapper objectMapper = new ObjectMapper();  
objectMapper.configOverride(boolean.class).setFormat(JsonFormat.Value.forShape(Shape.NUMBER_INT));
        
Sample sample1 = new Sample("foo", true);    
objectMapper.writeValue(new File("sample1.json"), sample1);

Resulted in this:

{"name":"foo","flag":true}

So, the conversion didn't work. The OP indicated the typical 1 and 0 values for true and false respectively. The mistake? .configOverride(Boolean.class) instead of .configOverride(boolean.class) should be used. After that change, sample1.json is now

{"name":"foo","flag":1}

Although that works, we run into a problem with deserialization. You can't just convert a 1 or 0 into a Boolean. For this, the best approach is to create serializer and deserializer classes that can be used to convert to/from Boolean. Then, these classes must be added as modules to the object mapper.


Deserializer

public class NumericBooleanDeserializer  extends JsonDeserializer<Boolean>{
    @Override
    public Boolean deserialize(JsonParser p, DeserializationContext ctxt)
      throws IOException {
        if ("1".equals(p.getText())) {
            return Boolean.TRUE;
        }
        if ("0".equals(p.getText())) {
            return Boolean.FALSE;
        }
        return null;
    }
}

Serializer

public class NumericBooleanSerializer extends JsonSerializer<Boolean>{
    @Override
    public void serialize(Boolean value, JsonGenerator gen, SerializerProvider serializers)
      throws IOException {
        gen.writeString(value ? "1" : "0");
    }
}

Now, we need to make two changes to Sample class. One, add a no-argument contructor, and annotate the boolean field.

public static class Sample {
    public Sample() {}
    
    public Sample(String name, boolean flag) {
        this.name = name;
        this.flag = flag;
    }

    private String name;

    @JsonSerialize(using = NumericBooleanSerializer.class)
    @JsonDeserialize(using = NumericBooleanDeserializer.class)
    private boolean flag;

// rest of the class omitted
}

Now, if I update my test main() method as follows

ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configOverride(Boolean.class).setFormat(JsonFormat.Value.forShape(Shape.NUMBER_INT));
SimpleModule module = new SimpleModule();
module.addSerializer(Boolean.class, new NumericBooleanSerializer());
        module.addDeserializer(Boolean.class, new NumericBooleanDeserializer());
        objectMapper.registerModule(module);
        
Sample sample1 = new Sample("foo", true);
System.out.println("Sample1: " + sample1);
objectMapper.writeValue(new File("sample1.json"), sample1);
        
Sample sample2 = objectMapper.readValue(Paths.get("sample1.json").toFile(), Sample.class);
System.out.println("Sample2: " + sample2);

The program outputs:

Sample1: { name :foo, flag : true }
Sample2: { name :foo, flag : true }

while the created file sample1.json contains the converted values for Boolean

{"name":"foo","flag":"1"}

UPDATE: For this simple example, the serializer is not really needed. The only difference between using the serializer and when you use the config override, is that the numeric value for the boolean is wrapped in double quotes when the serializer is used. The deserializer does not care about this small detail and it's able to convert the number to Boolean just the same. I just thought it was a good idea to show how to create and use the serializer.

Upvotes: 1

Related Questions