How to process an invalid value with Jackson JSON processor? - json

I use Jackson to proccess json.
Now, I face a proplem.
My POJO :
class Person{
public String name;
public int age;
}
And the JSON is
{"name":"Jackson","age":""}.
If I write the code like this:
Person person = mapper.readValue("{\"name\":\"Jackson\",\"age\":\"\"}", Person.class);
A Exception is thrown:
Can not construct instance of int from String value "": not a valid int value.
If the JSON is "{\"name\":\"Jackson\",\"age\":null}", it’s OK.
But now , I don’t want to modify the JSON. And how can I do ?

I recommend logging an issue at http://jira.codehaus.org/browse/JACKSON, requesting that this be considered a bug, or that a feature to allow proper handling is added. (Maybe it's reasonable that DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT would also allow deserialization of empty JSON strings to default primitive values, since that's how JSON null values are otherwise handled, when bound to Java primitives.) (Update: I logged issue 616 for this. Vote for it if you want it implemented.)
Until Jackson is so enhanced, custom deserialization processing would be necessary to transform a JSON empty string to a default primitive value (or to whatever non-string value is wanted). Following is such an example, which is fortunately simple, since the existing code to deserialize to an int already handles an empty string, turning it into 0.
import java.io.IOException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.deser.StdDeserializer;
import org.codehaus.jackson.map.module.SimpleModule;
public class Foo
{
public static void main(String[] args) throws Exception
{
// {"name":"Jackson","age":""}
String json = "{\"name\":\"Jackson\",\"age\":\"\"}";
SimpleModule module = new SimpleModule("EmptyJsonStringAsInt", Version.unknownVersion());
module.addDeserializer(int.class, new EmptyJsonStringAsIntDeserializer(int.class));
ObjectMapper mapper = new ObjectMapper().withModule(module);
Person p = mapper.readValue(json, Person.class);
System.out.println(mapper.writeValueAsString(p));
// {"name":"Jackson","age":0}
}
}
class Person
{
public String name;
public int age;
}
class EmptyJsonStringAsIntDeserializer extends StdDeserializer<Integer>
{
protected EmptyJsonStringAsIntDeserializer(Class<?> vc)
{
super(vc);
}
#Override
public Integer deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
return super._parseIntPrimitive(jp, ctxt);
}
}
(Also, note that if the target type were Integer instead of int, then the field would be populated with a null value (not that that's what's wanted). For this, I logged issue 617, to request a deserialization configuration to automatically set the primitive default value from a JSON null value, when binding to a primitive wrapper type field. In other words, when deserializing from a JSON null value to an Integer field, the target field would be set to Integer.valueOf(0) instead of null.)

Related

Flink Kafka - Custom Class Data is always null

Custom Class
Person
class Person
{
private Integer id;
private String name;
//getters and setters
}
Kafka Flink Connector
TypeInformation<Person> info = TypeInformation.of(Person.class);
TypeInformationSerializationSchema schema = new TypeInformationSerializationSchema(info, new ExecutionConfig());
DataStream<Person> input = env.addSource( new FlinkKafkaConsumer08<>("persons", schema , getKafkaProperties()));
Now if I send the below json
{ "id" : 1, "name": Synd }
through Kafka Console Producer, the flink code throws null pointer exception
But if I use SimpleStringSchema instead of CustomSchema as defined before, the stream is getting printed.
What is wrong in the above setup
The TypeInformationSerializationSchema is a de-/serialization schema which uses Flink's serialization stack and, thus, also its serializer. Therefore, when using this SerializationSchema Flink expects that the data has been serialized with Flink's serializer for the Person type.
Given the excerpt of the Person class, Flink will most likely use its PojoTypeSerializer. Feeding JSON input data won't be understood by this serializer.
If you want to use JSON as the input format, then you have to define your own DeserializationSchema which can parse JSON into Person.
Answer for who have the same question
Custom Serializer
class PersonSchema implements DeserializationSchema<Person>{
private ObjectMapper mapper = new ObjectMapper(); //com.fasterxml.jackson.databind.ObjectMapper;
#Override
public Person deserialize(byte[] bytes) throws IOException {
return mapper.readValue( bytes, Person.class );
}
#Override
public boolean isEndOfStream(Person person) {
return false;
}
#Override
public TypeInformation<Person> getProducedType() {
return TypeInformation.of(new TypeHint<Person>(){});
}
}
Using the schema
DataStream<Person> input = env.addSource( new FlinkKafkaConsumer08<>("persons", new PersonSchema() , getKafkaProperties()));

Can I override Gson's built-in number converters directly (not by delegation)?

I am using Gson to convert my JSON data to a Map<String, Object>. My JSON has some integer (actually long) fields, and I want them to be parsed as long (obviously). However, Gson parses them as doubles. How can I make Gson parse them as longs/integers? I've seen How to deserialize a list using GSON or another JSON library in Java? but I don't want to create a strongly-typed custom class, I'll be using a Map. I've also seen Android: Gson deserializes Integer as Double and a few other questions which I've thought might be duplicates, but all the answers either point to creating a strongly-typed class, creating extra functions that play role in deserialization or using completely another library.
Isn't there a simple way in Google's own JSON serializer/deserializer that will simply deserialize an integer (yeah, a number without a dot at all) as an integer and not double, as it should have been as default in the first place? If I wanted to send a floating point, I'd be sending 2.0, not 2 from my server JSON. Why on Earth am I getting a double and how do I get rid of it?
UPDATE: Even though I've clearly explained, some people still don't understand the simple fact that I am not in for another library (e.g. Jackson) and I'm aware of the simple fact that any parser should be able to identify 2.0 as a floating-point and 2 as a pure integer and parse accordingly, so please stop pointing me to telling why it's that way because it's simply incorrect and is not an excuse not to parse integers correctly. So, no, this is not a duplicate of Gson. Deserialize integers as integers and not as doubles.
You can't.
Long answer
You can't override gson's built-in numbers converters.
I've made a short code test to peek under the hood which types gson tries to find a delegated converter.
package net.sargue.gson;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.intellij.lang.annotations.Language;
import java.io.IOException;
import java.util.Map;
public class SO36528727 {
public static void main(String[] args) {
#Language("JSON")
String json = "{\n" +
" \"anInteger\": 2,\n" +
" \"aDouble\": 2.0\n" +
"}";
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new LongDeserializerFactory())
.create();
Map<String, Object> m =
gson.fromJson(json,
new TypeToken<Map<String, Object>>() {}.getType());
System.out.println(m.get("aDouble").getClass());
System.out.println(m.get("anInteger").getClass());
}
private static class LongDeserializerFactory
implements TypeAdapterFactory
{
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
System.out.println("type = " + type);
if (type.getRawType().equals(String.class)) {
TypeAdapter<String> stringAdapter =
gson.getDelegateAdapter(this, TypeToken.get(String.class));
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) throws IOException {
stringAdapter.write(out, (String) value);
}
#SuppressWarnings("unchecked")
#Override
public T read(JsonReader in) throws IOException {
String s = stringAdapter.read(in);
System.out.println("s = " + s);
return (T) s;
}
};
} else
return null;
}
}
}
The execution result is this:
type = java.util.Map<java.lang.String, java.lang.Object>
type = java.lang.String
s = anInteger
s = aDouble
class java.lang.Double
class java.lang.Double
So, you can see that gson looks just for two converters: the whole Map<> thing and the basic String. But no Double or Integer or Number or even Object. So you CAN'T override it unless you override it from a higher place like when dealing with a Map. And that was answered on the thread you reference on the question.

JSON Unmarshalling of xs:string

Problem:
We are facing strange problems when marshalling JSONs objects including the following content {"#type":"xs:string"}. Marshalling of this object results in a NullPointerException. See the stack trace below:
java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.SAX2DOM.startElement(SAX2DOM.java:204)
at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.closeStartTag(ToXMLSAXHandler.java:208)
at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.characters(ToXMLSAXHandler.java:528)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerHandlerImpl.characters(TransformerHandlerImpl.java:172)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.DomLoader.text(DomLoader.java:128)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.text(UnmarshallingContext.java:499)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.InterningXmlVisitor.text(InterningXmlVisitor.java:78)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector.processText(StAXStreamConnector.java:324)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector.handleEndElement(StAXStreamConnector.java:202)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector.bridge(StAXStreamConnector.java:171)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:355)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:334)
at com.sun.jersey.json.impl.BaseJSONUnmarshaller.unmarshalJAXBElementFromJSON(BaseJSONUnmarshaller.java:108)
at com.sun.jersey.json.impl.BaseJSONUnmarshaller.unmarshalFromJSON(BaseJSONUnmarshaller.java:97)
at JerseyNPETest.testNPEUnmarshal(JerseyNPETest.java:20)
The problem occurs while getting the response from the external service and casting it implicity by glassfish (Simple REST call).
We investigated the problem and found that it is actually related to the JSON unmarshaller.
Testcase:
Marshalling -
To verify our finding, we created a class which contains a member of type Object named propertyA. Then we set the value of propertyA to "some value" and marshalled it using the default marshaller which results in the JSON string "{"#type":"xs:string","$":"some value"}".
Unmarshalling - Afterwards we used the default unmarsahller. The attempt to unmarshall this JSON string resulted in the mentioned exception.
See the test case below:
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.json.impl.BaseJSONUnmarshaller;
import org.junit.Test;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.StringReader;
public class JerseyNPETest {
private static final String ERROR = "{\"additionalObject\":{\"#type\":\"xs:string\",\"$\":\"some value\"}}";
#Test
public void testNPEUnmarshal() throws JAXBException {
final JAXBContext context = JAXBContext.newInstance(AnObject.class);
final JSONConfiguration jsonConfig = JSONConfiguration.DEFAULT;
final BaseJSONUnmarshaller unmarshaller = new BaseJSONUnmarshaller(context, jsonConfig);
final StringReader reader = new StringReader(ERROR);
final AnObject result = unmarshaller.unmarshalFromJSON(reader, AnObject.class);
}
#XmlRootElement
public static class AnObject {
private Object additionalObject;
public Object getAdditionalObject() {
return additionalObject;
}
public void setAdditionalObject(final Object additionalObject) {
this.additionalObject = additionalObject;
}
}
}
Question:
How could this be solved in general e.g. by some configuration of glassfish to avoid this issue in the first place?
Currently we are working with glassfish 3.1.2.2. Any help is much appreciated!

Jax-rs unmarshal json - custom type

Using jax-rs, I'm not sure how to manually unmarshal JSON into my custom Java objects.
From my browser I'm sending a simple put request with the following JSON:
{"myDate":{"dayOfMonth":23, "monthOfYear":7, "year":2011}}
On the server I have a BlahResource which consumes this JSON and prints out the Java object properties:
#Component
#Scope("request")
#Path("/blah")
#Consumes("application/json")
#Produces("application/json")
public class BlahResource {
#PUT
public String putBlah(Blah blah) {
System.out.println("Value: " + blah.getMyDate().getMonthOfYear() + "/" + blah.getMyDate().getDayOfMonth() + "/" + blah.getMyDate().getYear());
return "{}";
}
}
Here's the source code for Blah:
public class Blah {
private LocalDate myDate;
public Blah()
{
}
public void setMyDate(LocalDate myDate)
{
this.myDate = myDate;
}
public LocalDate getMyDate()
{
return myDate;
}
}
The problem is Blah.myDate is a Joda-time LocalDate class which does not have setters for dayOfMonth, monthOfYear, and year. So for instance, when I run this the following exception is thrown:
Jul 10, 2011 8:40:33 AM
com.sun.jersey.spi.container.ContainerResponse mapMappableContainerException
SEVERE: The exception contained within MappableContainerException could not
be mapped to a response, re-throwing to the HTTP container
org.codehaus.jackson.map.exc.UnrecognizedPropertyException:
Unrecognized field "dayOfMonth"
This makes perfect sense to me. The problem is I have no idea how to write some sort of adapter so that whenever the type LocalDate is encountered, my adapter class is used to convert the JSON into a LocalDate.
Ideally, I want to do something like this:
public class LocalDateAdapter {
public LocalDate convert(String json)
{
int dayOfMonth = (Integer)SomeJsonUtility.extract("dayOfMonth");
int year = (Integer)SomeJsonUtility.extract("year");
int monthOfYear = (Integer)SomeJsonUtility.extract("monthOfYear");
return new LocalDate(year, monthOfYear, dayOfMonth);
}
}
UPDATE
I've now tried two methods, neither seem to be working.
1) Using ObjectMapper
It seems all I need to do is get a handle on the ObjectMapper and add a deserializer. So I created this provider. To my surprise, I named my dserializer: LocalDateDeserializer and when I had eclipse auto-fix imports I was shocked to see that Jackson already provides an extension for Joda. When I start the server, it finds the provider, but otherwise it seems this code is never invoked.
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ext.JodaDeserializers.LocalDateDeserializer;
import org.codehaus.jackson.map.module.SimpleModule;
import org.joda.time.LocalDate;
import org.springframework.stereotype.Component;
#Component
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type) {
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null))
.addDeserializer(LocalDate.class, new LocalDateDeserializer());
mapper.registerModule(testModule);
return mapper;
}
}
2) The second method I tried is to specify a #JsonDeserialize annotation directly on the field.
#JsonDeserialize(using = CustomDateDeserializer.class)
private LocalDate myDate;
This also didn't seem to be invoked.
public class CustomDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException
{
return new LocalDate(2008, 2, 5);
}
}
I'm not sure what to do. This seems like a very basic problem.
UPDATE 2
I'm considering dropping Jackson for using deserialization (even though it works fairly well with Jersey).
I was already using flexjson for serialization, and it seems flexjson is just as simple for deserialization. All these other libraries have some much abstraction and unnecessary complexity.
In Flexjson, I just had to implement ObjectFactory:
class LocalDateTransformer implements ObjectFactory {
#Override
public Object instantiate(ObjectBinder context, Object value, Type targetType, Class targetClass)
{
HashMap map = (HashMap)value;
int year = (Integer)map.get("year");
int monthOfYear = (Integer)map.get("monthOfYear");
int dayOfMonth = (Integer)map.get("dayOfMonth");
return new LocalDate(year, monthOfYear, dayOfMonth);
}
}
It looks surprisingly like the "adapter" class I originally posted! And my resource method now becomes:
#PUT
public String putBlah(String blahStr) {
Blah blah = new JSONDeserializer<Blah>().use(LocalDate.class, new LocalDateTransformer()).deserialize(blahStr, Blah.class);
}

Is there a possibility to hide the "#type" entry when marshalling subclasses to JSON using EclipseLink MOXy (JAXB)?

I'm about to develop a JAX-RS based RESTful web service and I use MOXy (JAXB) in order to automatically generate my web service's JSON responses.
Everything is cool, but due to the fact that the web service will be the back-end of a JavaScript-based web application and therefore publicly accessible I don't want to expose certain details like class names, etc.
But, I've realized that under certain conditions MOXy embeds a "#type" entry into the marshalled string and this entry is followed by the class name of the object that has just been marshalled.
In particular, I've realized that MOXy behaves in this way when marshalling instances of extended classes.
Assume the following super class "MyBasicResponse"
#XmlRootElement(name="res")
public class MyBasicResponse {
#XmlElement
private String msg;
public MyBasicResponse() {
// Just for conformity
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
And this specialized (extended) class "MySpecialResponse"
#XmlRootElement(name="res")
public class MySpecialResponse extends MyBasicResponse {
#XmlElement
private String moreInfo;
public MySpecialResponse() {
// Just for conformity
}
public String getMoreInfo() {
return moreInfo;
}
public void setMoreInfo(String moreInfo) {
this.moreInfo = moreInfo;
}
}
So, the MyBasicResponse object's marshalled string is
{"msg":"A Message."}
(That's okay!)
But, the MySpecialResponse object's marshalled string is like
{"#type":"MySpecialResponse","msg":"A Message.","moreInfo":"More Information."}
Is there a way to strip the
"#type":"MySpecialResponse"
out of my response?
You can wrap your object in an instance of JAXBElement specifying the subclass being marshalled to get rid of the type key. Below is a full example.
Java Model
Same as from the question, but with the following package-info class added to specifying the field access to match those classes
#XmlAccessorType(XmlAccessType.FIELD)
package com.example.foo;
import javax.xml.bind.annotation.*;
Demo Code
Demo
import java.util.*;
import javax.xml.bind.*;
import javax.xml.namespace.QName;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {MySpecialResponse.class}, properties);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
MySpecialResponse msr = new MySpecialResponse();
marshaller.marshal(msr, System.out);
JAXBElement<MySpecialResponse> jaxbElement = new JAXBElement(new QName(""), MySpecialResponse.class, msr);
marshaller.marshal(jaxbElement, System.out);
}
}
Output
We see that when the object was marshalled an type key was marshalled (corresponding to the xsi:type attribute in the XML representation), because as MOXy is concerned it was necessary to distinguish between MyBasicResponse and MySpecialResponse. When we wrapped the object in an instance of JAXBElement and qualified the type MOXy didn't need to add the type key.
{
"type" : "mySpecialResponse"
}
{
}
For More Information
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html