Convert enum to JSON String with Jackson - java

Im trying to convert an Enum class into a JSON string using jackson, the problem is the class is in a jar file so I am looking for better soultion then changing it.
when I use this code I get the following output:
Code
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
BrainWave brainwave = BrainWave.DELTA;
brainwave.value(50);
System.out.println(ow.writeValueAsString(brainwave));
Output
"DELTA"
The output I want:
{
"type" : 1,
"value" : 50
}
I know i can use #JsonFormat but As I stated before, I rather not change the jar file.

Try a StdDeserializer - this article on Enum Serialization shows the different ways but in your case you'll want something like this (this is a rewrite of their example at the bottom based on your snippet above)
public class DeltaEnumDeserializer extends StdDeserializer<DELTA> {
#Override
public Distance deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
final JsonNode node = jsonParser.getCodec().readTree(jsonParser);
final int type = node.get("type").asInt();
final int value = node.get("value").asInt();
for (final DELTA curr : DELTA.values()) {
if (curr.type == type && curr.value == value) {
return curr;
}
}
return null;
}
}
This article shows a snippet of code on linking up the deserializer. ie.
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule();
module.addDeserializer(DELTA.class, new DeltaEnumDeserializer());
mapper.registerModule(module);
DELTA readValue = mapper.readValue(json, DELTA.class);

Related

ObjectWriter print json double with precision [duplicate]

Suppose I have an object with
private Double test;
// Need specific output in JSON via Jackson: test = 24.6000
When output to JSON via Jackson, I get 24.6, but I need the exact 4-decimal output as in the example. Does Jackson allow this?
For example, for Dates, we found a way to force MM/dd/yyyy:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "MM/dd/yyyy")
Date myDate;
We need something similar for Decimal formatting.
One way of doing this is to use custom json serializer and specify in #JsonSerialize.
#JsonSerialize(using = CustomDoubleSerializer.class)
public Double getAmount()
public class CustomDoubleSerializer extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
if (null == value) {
jgen.writeNull();
} else {
final String pattern = ".####";
final DecimalFormat myFormatter = new DecimalFormat(pattern);
final String output = myFormatter.format(value);
jgen.writeNumber(output);
}
}
}
You can try to use com.fasterxml.jackson.databind.util.RawValue:
BigDecimal d = new BigDecimal(new BigInteger("246000"), 4);
RawValue rv = new RawValue(d.toPlainString());
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode output = objectMapper.createObjectNode();
output.putRawValue("decimal_value", rv);
System.out.println(output.toPrettyString());
//output is:
//{
// "decimal_value" : 24.6000
//}

Jackson cannot deserialize empty byte array/stream

Can I somehow alter ObjectMapper to be able to handle null and empty values?
Let's say that my value is read as
objectMapper.readValue(val, new TypeReference<Object>() {});
Where val is
val = new ByteArrayInputStream(new byte[] {});
I don't have control over value that is passed and I cannot check for buffer length prior to executing readValue.
I've tried configuring mapper with DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT such as:
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
But I still get the com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input error. Is it possible to somehow have Jackson ignore empty values and just return null?
Is it possible to somehow have Jackson ignore empty values and just return null?
You can successfully dial with an empty byte array, or an empty input stream, by using a more low-level streaming API.
That's the core idea of how you can ensure that there's some to parse by employing a JsonParser before feeding the data into an ObjectMapper:
byte[] jsonBytes1 = {};
ObjectMapper mapper = new ObjectMapper();
JsonParser parser = mapper.getFactory().createParser(jsonBytes1);
JsonNode node = parser.readValueAsTree();
MyPojo myPojo = null;
if (node != null) {
myPojo = mapper.treeToValue(node, MyPojo.class);
}
So we're parsing the input into a JsonNode and checking it manually, only if it's not null ObjectMapper comes into play.
If we extract this logic into a separate method, that's it might look like (Java 8 Optional might be handy in this case a return type):
public static <T> Optional<T> convertBytes(byte[] arr,
Class<T> pojoClass,
ObjectMapper mapper) throws IOException {
JsonParser parser = mapper.getFactory().createParser(arr);
JsonNode node = parser.readValueAsTree();
return node != null ? Optional.of(mapper.treeToValue(node, pojoClass)) : Optional.empty();
}
Usage example
Consider a simple POJO:
public class MyPojo {
private String name;
// getter, setters and toString
}
main()
public static void main(String[] args) throws IOException {
String source = """
{
"name" : "Alice"
}
""";
byte[] jsonBytes1 = {};
byte[] jsonBytes2 = source.getBytes(StandardCharsets.UTF_8);
ObjectMapper mapper = new ObjectMapper();
System.out.println(convertBytes(jsonBytes1, MyPojo.class, mapper));
System.out.println(convertBytes(jsonBytes2, MyPojo.class, mapper));
}
Output:
Optional.empty
Optional[Test.MyPojo(name=Alice)]

JsonNode comparison excluding fields

I'm trying to deserialise a JSON string using ObjectMapper (Jackson) and exclude a field while performing the deserialisation.
My code is as follows:
String aContent = new String(Files.readAllBytes(Paths.get(aFile)));
String bContent = new String(Files.readAllBytes(Paths.get(bFile)));
ObjectMapper mapper = new ObjectMapper();
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("_idFilter", SimpleBeanPropertyFilter.serializeAllExcept("_id"));
mapper.setFilterProvider(filterProvider);
JsonNode tree1 = mapper.readTree(aContent);
JsonNode tree2 = mapper.readTree(bContent);
String x = mapper.writeValueAsString(tree1);
return tree1.equals(tree2);
Both x and tree1 and tree2 contains the value _id in the JSON String but it isn't removed.
You are following Ignore Fields Using Filters except the first step
First, we need to define the filter on the java object:
#JsonFilter("myFilter")
public class MyDtoWithFilter { ... }
So currently you supposed to add
#JsonFilter("_idFilter")
public class JsonNode {
It's not possible so you need to create a class that extends JsonNode and use it instead
#JsonFilter("_idFilter")
public class MyJsonNode extends JsonNode {
If you don't want to implement all abstract method define as abstract
#JsonFilter("_idFilter")
public abstract class MyJsonNode extends JsonNode {
}
And in your code:
MyJsonNode tree1 = (MyJsonNode) mapper.readTree(aContent);
MyJsonNode tree2 = (MyJsonNode) mapper.readTree(bContent);
FilterProvider are meant to be used with custom Object like here
If you want to stick with JsonNode, use this method :
String aContent = new String("{\"a\":1,\"b\":2,\"_id\":\"id\"}");
ObjectMapper mapper = new ObjectMapper();
JsonNode tree1 = mapper.readTree(aContent);
ObjectNode object = (ObjectNode) tree1;
object.remove(Arrays.asList("_id"));
System.out.println(mapper.writeValueAsString(object));
Will print :
{"a":1,"b":2}
If you use Jackson 2.6 or higher, you can use a FilteringParserDelegate with a custom TokenFilter.
public class PropertyBasedIgnoreFilter extends TokenFilter {
protected Set<String> ignoreProperties;
public PropertyBasedIgnoreFilter(String... properties) {
ignoreProperties = new HashSet<>(Arrays.asList(properties));
}
#Override
public TokenFilter includeProperty(String name) {
if (ignoreProperties.contains(name)) {
return null;
}
return INCLUDE_ALL;
}
}
When creating the FilteringParserDelegate with this PropertyBasedIgnoreFilter, make sure to set the booleans includePath and allowMultipleMatches both to true.
public class Main {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String jsonInput = "{\"_p1\":1,\"_p2\":2,\"_id\":\"id\",\"_p3\":{\"_p3.1\":3.1,\"_id\":\"id\"}}";
JsonParser filteredParser = new FilteringParserDelegate(mapper.getFactory().createParser(new ByteArrayInputStream(jsonInput.getBytes())),
new PropertyBasedIgnoreFilter("_id"),
true,
true);
JsonNode tree = mapper.readTree(filteredParser);
System.out.println(jsonInput);
System.out.println(tree);
System.out.println(jsonInput.equals(tree.toString()));
}
}
prints
{"_p1":1,"_p2":2,"_id":"id","_p3":{"_p3.1":3.1,"_id":"id"}}
{"_p1":1,"_p2":2,"_p3":{"_p3.1":3.1,"_id":"id"}}
false
As you can see, nested occurrences of _idare not filtered out. In case that's not what you need, you can of course extend my PropertyBasedIgnoreFilter with your own TokenFilter implementation.

How to deserialize some JSON parameters as one new variable in DTO

How to deserialize JSON (using Jackson) into Java object if some JSON parameters should be used to create variable of Java DTO(but not be deserialized as dto variables).
For example I have JSON {"sideA" : 2, "sideB" : 4, "useless_parameter" : "useless_information"} and I need to get result of toString (of deserialized Java object) something like : RectangleDto{area = 8, useless_parameter = "useless_information"}
If I need to deserialize "useless_parameter" then I can use #JsonGetter("useless_information"), so what should I do with "sideA" and "sideB" if I need to take area as variable of RectangleDto? I already have a method for conversion JSON parameters into this variable.
Probably you need a custom deserializer:
class MyDeserializer extends StdDeserializer<RectangleDto> {
public MyDeserializer() {
this(null);
}
protected MyDeserializer(Class<?> vc) {
super(vc);
}
#Override
public RectangleDto deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int a = node.get("sideA").intValue();
int b = node.get("sideB").intValue();
String useless_parameter = node.get("useless_parameter").asText();
RectangleDto test = new RectangleDto();
test.setArea(a * b);
test.setUseless_parameter(useless_parameter);
return test;
}
}
Then register the deserializer on the class
#JsonDeserialize(using = MyDeserializer.class)
public class RectangleDto {
private int area;
private String useless_parameter;
// getters, setters...
}
Then this will work as you want:
RectangleDto r = mapper.readValue("{\"sideA\" : 2, \"sideB\" : 4, \"useless_parameter\" : \"useless_information\"}", RectangleDto.class);
System.out.println(r);
results in
RectangleDto{area=8, useless_parameter='useless_information'}
You could add #JsonProperty for the area and also use #JsonIgnore for sideA and SideB. Please check
Jackson :: adding extra fields to an object in serialization

Json deserializer that works across different classes in java

I am trying to create an #JsonDeserializer that will work across classes. I am using JAX-RS and the incoming json string will have fields in snake case. I want to override the json deserialization so that my java objects do not have snake-case fields. Since the creation of the java object is happening within JAX-RS, I am using the #JsonDeserializer annotation on all my request classes. My current implementation has a generic base class, but I need to extend it for all the concrete classes so that I can pass in the actual class I want to create. Is there any way to do this more generically?
For example, I have multiple request objects like this:
#JsonDeserialize(using = MyRequestDeserializer.class)
public class MyRequest {
....
}
I have created a generic deserializer like so:
public class GenericRequestDeserializer extends JsonDeserializer<Object> {
private static ObjectMapper objectMapper = new ObjectMapper();
#Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return null;
}
protected Object deserializeIt(JsonParser jsonParser, Class cls) {
try {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
Iterator<String> fieldNames = node.fieldNames();
Object object = cls.newInstance();
while(fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode value = node.get(fieldName);
String newFieldName = convertFieldName(fieldName);
//TODO: currently failing if I do not find a field, should the exception be swallowed?
Class returnType = object.getClass().getMethod("get" + newFieldName).getReturnType();
Method setMethod = object.getClass().getMethod("set" + newFieldName, returnType);
Object valueToSet = null;
if(value.isTextual()) {
valueToSet = value.asText();
} else if(value.isContainerNode()) {
valueToSet = objectMapper.readValue(value.toString(), returnType);
} else if (value.isInt()) {
valueToSet = value.asInt();
}
setMethod.invoke(object, valueToSet);
}
return object;
} catch (Exception e) {
throw new TokenizationException(GatewayConstants.STATUS_SYSTEM_ERROR,
"Error in deserializeIt for " + cls.getSimpleName() + " caused by " + e.getMessage(), e);
}
}
private String convertFieldName(String fieldName) {
StringBuilder newFieldName = new StringBuilder();
int length = fieldName.length();
boolean capitalize = true; //first character should be capitalized
for(int i = 0; i < length; i++) {
char current = fieldName.charAt(i);
if(current == '_') {
//remove the underscore and capitalize the next character in line
capitalize = true;
} else if(capitalize) {
newFieldName.append(Character.toUpperCase(current));
capitalize = false;
} else {
newFieldName.append(current);
}
}
return newFieldName.toString();
}
}
But I still need to create a new class per Request in order to pass in the proper class to create:
public class MyRequestDeserializer extends GenericRequestDeserializer {
#Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return deserializeIt(jsonParser, MyRequest.class);
}
}
Is there any way to get rid of all the MyRequestDeserializer classes? In other words, can the GenericRequestDeserializer figure out what class it is actually deserializing?
So I found a much better option for changing all my objects to snake case. Instead of using Serializers and Deserializers on each class, I was able to inject an ObjectMapper into the JsonProvider in Spring. ObjectMapper already supports a property that will do the camel-case to snake-case automagically. I just needed to overwrite the getSingletons method in my class that extends Application like so:
public class MyApp extends Application {
....
#Override
public Set<Object> getSingletons() {
final Set<Object> objects = new LinkedHashSet<Object>();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objects.add(new JacksonJsonProvider(objectMapper));
return objects;
}
}

Categories