Jackson cannot deserialize empty byte array/stream - java

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)]

Related

Convert enum to JSON String with Jackson

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);

Convert NaN value into null when parsing json using jackson library

What is the best way to parse a json string in to a JsonNode object, and convert all the NaN value in to null ? The following code will convert the Nan to DoubleNode NaN. I try to register a custom deserializer but it didn't pick up the Nan node.
JsonMapper mapper = JsonMapper.builder()
.enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build();
final String testJson = "{\"key\":NaN}";
JsonNode node = mapper.readTree(testJson)
One way it could be done is like below
Create some POJO class with #JsonSetter on setter method.
public class KeyPojo {
private Double key;
#JsonSetter
public void setKey(Double key) {
if (Double.isNaN(key)) {
this.key = null;
} else {
this.key = key;
}
}
}
And parse json text like below.
KeyPojo keyObj = mapper.readValue(testJson, KeyPojo.class);
now, keyObj.key contains NULL value.

How to convert a json string to an object

here is my pojo
public class Data{
List<Object> objects;
String owneruid;
}
if the out put is pure json like this
{"object":[{"p1":100,"p2":"name","p3":"sfa0","p4":300}],"owneruid":"owneruid"}
then iam able to convert with no worries but
here is my output
{
"object":"[{\"p1\":32,\"p3\":470,\"p3\":\"213\",\"p4\":\"name\"}]",
"owneruid":"6697729776330393738"
}
im converting a json string to string because to store in my db as it does not accept json so when i query returns like above so every time i need to fetch the value and convert it to json object and put it in list and display. can you suggest me a better approach.
And when i try to convert a list of custom classes to json using GSON
ArrayList<Object> list=new ArrayList<>();
Object object=new Object();
object.setP1(3);
object.setP2(4);
list.add(object);
Gson gson=new Gson();
String json = gson.toJson(list);
Required:
{"object":[{"p1":100,"p2":"name","p2":"sfa0","p4":300}],"owneruid":"owneruid"}
buts it ends like this
{"object":"[{\"p1\":313,\"p2\":470,\"p3\":\"1521739327417\",\"p4\":\"name\"}]","owneruid":"6697729776330393738"}
You have to use any json frameworks. E.g. Jackson or Gson. As alternative you could do smth. like this. Just evaluate JavaScript.
public static void main(String... args) throws ScriptException {
ScriptEngine js = new ScriptEngineManager().getEngineByName("javascript");
Object obj = js.eval("[{\"width\":313,\"height\":470,\"mediauid\":\"1521739327417\",\"mediatype\":\"image\"}]");
// res is either List<Object> or Map<String, Object>
Object res = convertIntoJavaObject(obj);
}
private static Object convertIntoJavaObject(Object obj) {
if (!(obj instanceof ScriptObjectMirror))
return obj;
ScriptObjectMirror mirror = (ScriptObjectMirror)obj;
if (mirror.isArray())
return mirror.entrySet().stream()
.map(entry -> convertIntoJavaObject(entry.getValue()))
.collect(Collectors.toList());
Map<String, Object> map = new HashMap<>();
mirror.entrySet().forEach((key, value) -> map.put(key, convertIntoJavaObject(value)));
return map;
}
You can use the below code snippet as it seems fit for your case.
ObjectMapper can be found with Jackson framework. inputJson is the JSON string you have mentioned.
ObjectMapper mapper = new ObjectMapper();
Object mediaMetaDataObj = mapper.readValue( inputJson, Object.class );
Hope this helps.

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 use Jackson to deserialize an array of objects when we have written custom deserializer

i have written one custom deserializer , which will take the string and deserialize it
public static class MyDeserializer extends JsonDeserializer {
#Override
public MyClass deserialize(JsonParser parser,
DeserializationContext deserializer) throws IOException {
Myclass obj = new Myclass();
while(!parser.isClosed()) {
JsonToken jsonToken = parser.nextToken();
if (JsonToken.FIELD_NAME.equals(jsonToken)) {
String fieldName = parser.getCurrentName();
System.out.println(fieldName);
jsonToken = parser.nextToken();
switch(fieldName) {
-----set the fields of obj
}
}
return obj
}
This can serialize one object at a time , i want to create a list of objects
tried this
JavaType mapType = MAPPER.getTypeFactory().constructCollectionType(List.class, Myclass.class);
List <MyClass> mylist = (List<MyClass>)MAPPER.readValue(jsonString,mapType);
This is not working and no error is thrown , just stuck while deserializing it
Do we need to split json array and call deserializer for every object or modify the custom deserializer to create list of objects
Hm, I do not get why there is a need for a custom deserializer at all.
IMHO you could just do:
List <MyClass> mylist = MAPPER.readValue(jsonString, new TypeReference<List<MyClass>>() {});

Categories