Recursive type conversion jackson json parser - java

I'm using Jackson to parse json string to scala case class instance.
Here's my code
import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import scala.reflect.{ClassTag, _}
object JsonUtil {
val jacksonMapper = new ObjectMapper()
jacksonMapper.registerModule(DefaultScalaModule)
jacksonMapper.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
jacksonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
def toJson(value: Map[Symbol, Any]): String = {
toJson(value map { case (k,v) => k.name -> v})
}
def toJson(value: Any): String = {
jacksonMapper.writeValueAsString(value)
}
def fromJson[T: ClassTag](json: String): T = {
jacksonMapper.readValue[T](json, classTag[T].runtimeClass.asInstanceOf[Class[T]])
}
}
and this's the json parsing error
case class Person(name: String, age: Long, score: List[Long])
val person = JsonUtil.fromJson[Person]("""{"name": 123654,"age":23, "score": [6,7,9]}""")
person.name
person.score
person.score.head
res0: String = 123654
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
at scala.runtime.BoxesRunTime.unboxToLong(ws.sc93376:101)
at #worksheet#.#worksheet#(ws.sc93376:35)
I know jackson is smart enough to convert between numeric types, string to numeric and vice versa, but seemingly it only works for non-collection situation.
How can I do better, that forces jackson recursively converts type inside collection?

You have to add this annotation before score: List[Long]:
#JsonDeserialize(contentAs = classOf[java.lang.Long])
Source: https://github.com/FasterXML/jackson-module-scala/wiki/FAQ

Related

gson.toJson returns an empty object for a data class

I have a kotlin data class OfflineDataRequestInfo which I want to convert to Json using Gson, but it always returns an empty object
data class OfflineDataRequestInfo (
#SerializedName("status") val status: String,
#SerializedName("userId") val userId: String?,
#SerializedName("fulOrderId") val fulOrderId: String,
#SerializedName("timeStamp") val timeStamp: String,
#SerializedName("fulOrder") val fulOrder: String,
#SerializedName("checks") val checks: String?
)
some of the values could be null so I tried the bellow code too which returned {}
gson.toJson(OfflineDataRequestInfo("a","b","c","d", "e", "f"))
Here is a bit more info just in case that is an issue
#Entity
data class OfflineData (
#PrimaryKey(autoGenerate = true) val id: Int = 0,
#ColumnInfo(name="request_code") val requestCode: String?,
#Embedded
val requestInfoJson: OfflineDataRequestInfo
)
this is my actual function
fun postFulOurderData(offlineData: OfflineData) {
if (offlineData != null) {
val mainRepository = MainRepository(ApiHelper(RetrofitBuilder.apiService))
val builder = GsonBuilder()
builder.serializeNulls()
val gson = builder.create()
launch {
val postFulOrder = mainRepository.postFulOrderOfflineData(gson.toJson(offlineData.requestInfoJson), tokenResult.access_token)
}
}
}
I also tried using GsonBuilder as shown above and also the default Gson, but no luck
also tried gson.toJson(offlineData.requestInfoJson, OfflineDataRequestInfo::class.java)
can any one suggest please where I am doing it wrong
your help will be very much appreciated
thanks
R
Probably you have new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create() setting for gson. If so you just need to add #Expose annotation to each field you want serialize in json:
data class OfflineDataRequestInfo (
#Expose #SerializedName("status") val status: String,
#Expose #SerializedName("userId") val userId: String?,
#Expose #SerializedName("fulOrderId") val fulOrderId: String,
#Expose #SerializedName("timeStamp") val timeStamp: String,
#Expose #SerializedName("fulOrder") val fulOrder: String,
#Expose #SerializedName("checks") val checks: String?
)
I don't know that much about Kotlin, but I do know that you can mix Java and Kotlin pretty well, and since Gson was made with Java use in mind I think you should use Java. You can create a Java class, and then a method like this:
String toJson(OfflineDataRequestInfo o) {
return new Gson().toJson(o, OfflineDataRequestInfo.class);
}
and then call that method from Kotlin.
I'm not saying that you should do your entire project in Java, just use Java for this one method.
Hope this helps, and please tell me if this wasn't what you had in mind :)
I just tested the following code using gson:2.2.4 and it works:
data class OfflineDataRequestInfo (
#SerializedName("status") val status: String,
#SerializedName("userId") val userId: String?,
#SerializedName("fulOrderId") val fulOrderId: String,
#SerializedName("timeStamp") val timeStamp: String,
#SerializedName("fulOrder") val fulOrder: String,
#SerializedName("checks") val checks: String?
)
fun main() {
println(Gson().toJson(OfflineDataRequestInfo("a","b","c","d", "e", "f")))
}
Output:
{"status":"a","userId":"b","fulOrderId":"c","timeStamp":"d","fulOrder":"e","checks":"f"}
I didn't try it on android, but it should also work.
I suggest you try to update your gson version and also to write a unit test using it.

How does Gson.fromJson(String, Class) work?

Consider following sample Json string :
{"Name":"val","FatherName":"val","MotherName":"val"}
I convert the above Json to the following pojo :
public class Info{
private String name;
private String father;
private String mother;
}
What I am wondering is, when I do the following :
Gson.fromJson(jsonLine, Info.class);
How are the keys in json object tracked to the variables in my pojo ? How is the value of the key FatherName stored in father in Info.class?
Gson is using the reflection(https://android.jlelse.eu/reflections-on-reflection-performance-impact-for-a-json-parser-on-android-d36318c0697c). So the example json will not work as expected in your example there are a couple of option to solve this
Change json string to {"name":"val","father":"val","mother":"val"}
Change the properties in the info class father-fatherName and the same for mother
3 Create a custom serializer GSON - Custom serializer in specific case
Thanks to #Aaron
you can also annotated the variables with #SerializedName
#SerializedName("father")
private string FatherName;
In Kotlin, having this class:
import com.google.gson.annotations.SerializedName
data class UsuariosDTO (
#SerializedName("data") var data: List<Usuarios>
)
data class Usuarios(
#SerializedName("uid") var uid: String,
#SerializedName("name") var name: String,
#SerializedName("email") var email: String,
#SerializedName("profile_pic") var profilePic: String,
#SerializedName("post") var post: Post
)
data class Post(
#SerializedName("id") var id: Int,
#SerializedName("date") var date: String,
#SerializedName("pics") var pics: List<String>
)
I'm able to use (where dataObteined is of UsuariosDTO type):
val json = gson.toJson(dataObtained)
And later deserialize the json like this:
val offUsuariosDTO = gson.fromJson(json, UsuariosDTO::class.java)
After looking everywhere I found out my error was UsuariosDTO.class. Since Gson is a java library you have to use UsuariosDTO::class.java

How to take specific value from key in JSON in Scala?

I want to return the value "stations" from the key "base" in the JSON below from the url https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22. How do I do that in Scala?
Here is what I attempted.
import scalaj.http._
import play.api.libs.json.{JsValue, Json}
object JSON {
def convertToJson(): String = {
val url: String = "https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22"
val response: String = Http(url).asString.body
val parsed: JsValue = Json.parse(response)
parsed.get(base)
}
}
You can use \ method returning a property corresponding a field name
val result: JsLookupResult = parsed \ "base"
If you want the result as string you can use as[String] conversion
println(result.as[String])
It will print "station", but it can throw an exception if it isn't really String
In case if value is missing you can use pattern matching:
result match {
case JsDefined(v) => println(v.toString) // will print "stations"
case undefined: JsUndefined => println(undefined.validationError) // prints error in case missing value
}

Passing type in Scala as an argument

I want to pass a type to a function in Scala.
Problem in detail
First iteration
I have the following Java classes (coming from an external source):
public class MyComplexType {
public String name;
public int number;
}
and
public class MyGeneric<T> {
public String myName;
public T myValue;
}
In this example I want MyComplexType to be the the actual type of MyGeneric; in the real problem there are several possibilities.
I want to deserialize a JSON string using a Scala code as follows:
import org.codehaus.jackson.map.ObjectMapper
object GenericExample {
def main(args: Array[String]) {
val jsonString = "{\"myName\":\"myNumber\",\"myValue\":{\"name\":\"fifteen\",\"number\":\"15\"}}"
val objectMapper = new ObjectMapper()
val myGeneric: MyGeneric[MyComplexType] = objectMapper.readValue(jsonString, classOf[MyGeneric[MyComplexType]])
val myComplexType: MyComplexType = myGeneric.myValue
}
}
it compiles fine but runtime error occurs:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to MyComplexType
at GenericExample$.main(GenericExample.scala:9)
Second iteration
Working solution to the problem:
val jsonString = "{\"myName\":\"myNumber\",\"myValue\":{\"name\":\"fifteen\",\"number\":\"15\"}}"
val objectMapper = new ObjectMapper()
val myGeneric: MyGeneric[MyComplexType] = objectMapper.readValue(jsonString, classOf[MyGeneric[MyComplexType]])
myGeneric.myValue = objectMapper.readValue(objectMapper.readTree(jsonString).get("myValue").toString, classOf[MyComplexType])
val myComplexType: MyComplexType = myGeneric.myValue
Not nice but works. (If anybody knows how to make it better, that would also welcome.)
Third iteration
The lines in the solution of second iteration occur in the real problem several times, therefore I want to create a function. The altering variables are the JSON formatted string and the MyComplexType.
I want something like this:
def main(args: Array[String]) {
val jsonString = "{\"myName\":\"myNumber\",\"myValue\":{\"name\":\"fifteen\",\"number\":\"15\"}}"
val myGeneric = extractMyGeneric[MyComplexType](jsonString)
val myComplexType: MyComplexType = myGeneric.myValue
}
private def extractMyGeneric[T](jsonString: String) = {
val objectMapper = new ObjectMapper()
val myGeneric = objectMapper.readValue(jsonString, classOf[MyGeneric[T]])
myGeneric.myValue = objectMapper.readValue(objectMapper.readTree(jsonString).get("myValue").toString, classOf[T])
myGeneric
}
This does not work (compiler error). I've already played around with various combinations of Class, ClassTag, classOf but none of them helped. There were compiler and runtime errors as well. Do you know how to pass and how to use such a type in Scala? Thank you!
When you use jackson to parse json, you can use TypeReference to parse generic type. Example:
val jsonString = "{\"myName\":\"myNumber\",\"myValue\":{\"name\":\"fifteen\",\"number\":\"15\"}}"
val objectMapper = new ObjectMapper()
val reference = new TypeReference[MyGeneric[MyComplexType]]() {}
val value: MyGeneric[MyComplexType] = objectMapper.readValue(jsonString, reference)
if you still want to use Jackson, I think you can create a parameter with TypeReference type. like:
implicit val typeReference = new TypeReference[MyGeneric[MyComplexType]] {}
val value = foo(jsonString)
println(value.myValue.name)
def foo[T](jsonStr: String)(implicit typeReference: TypeReference[MyGeneric[T]]): MyGeneric[T] = {
val objectMapper = new ObjectMapper()
objectMapper.readValue(jsonStr, typeReference)
}
Using your approach, I think this is how you can get classes that you need using ClassTags:
def extractMyGeneric[A : ClassTag](jsonString: String)(implicit generic: ClassTag[MyGeneric[A]]): MyGeneric[A] = {
val classOfA = implicitly[ClassTag[A]].runtimeClass.asInstanceOf[Class[A]]
val classOfMyGenericOfA = generic.runtimeClass.asInstanceOf[Class[MyGeneric[A]]]
val objectMapper = new ObjectMapper()
val myGeneric = objectMapper.readValue(jsonString, classOfMyGenericOfA)
myGeneric.myValue = objectMapper.readValue(objectMapper.readTree(jsonString).get("myValue").toString, classOfA)
myGeneric
}
I am not familiar with jackson but in play-json you could easily define Reads for your generic class like this
import play.api.libs.functional.syntax._
import play.api.libs.json._
implicit def genReads[A: Reads]: Reads[MyGeneric[A]] = (
(__ \ "myName").read[String] and
(__ \ "myValue").read[A]
)((name, value) => {
val e = new MyGeneric[A]
e.myName = name
e.myValue = value
e
})
Having this, and provided that instance of Reads for MyComplexType exists, you can implement your method as
def extractMyGeneric[A: Reads](jsonString: String): MyGeneric[A] = {
Json.parse(jsonString).as[MyGeneric[A]]
}
the issue here is that you need to provide Reads for all of your complex types, which would be as easy as
implicit complexReads: Reads[MyComplexType] = Json.reads[MyComplexType]
if those were case classes, otherways I think you would have to define them manually in simillar way to what I've done with genReads.

How to prevent Gson from converting a long number (a json string ) to scientific notation format?

I need to convert json string to java object and display it as a long. The json string is a fixed array of long numbers:
{numbers
[ 268627104, 485677888, 506884800 ] }
The code to convert works fine in all cases except for numbers ending in 0. It converts those to a scientific notation number format:
public static Object fromJson(HttpResponse response, Class<?> classOf)
throws IOException {
InputStream instream = response.getResponseInputStream();
Object obj = null;
try {
Reader reader = new InputStreamReader(instream, HTTP.UTF_8);
Gson gson = new Gson();
obj = gson.fromJson(reader, classOf);
Logger.d(TAG, "json --> "+gson.toJson(obj));
} catch (UnsupportedEncodingException e) {
Logger.e(TAG, "unsupported encoding", e);
} catch (Exception e) {
Logger.e(TAG, "json parsing error", e);
}
return obj;
}
The actual result:
Java object : 268627104, 485677888, 5.068848E+8
Notice the last number is converted to a scientific notation format. Can anyone suggest what could be done to work around it or prevent it or undo it? I'm using Gson v1.7.1
If serializing to a String is an option for you, you can configure GSON to do so with:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setLongSerializationPolicy( LongSerializationPolicy.STRING );
Gson gson = gsonBuilder.create();
This will produce something like:
{numbers : [ "268627104", "485677888", "506884800" ] }
Another work around is to use the JsonParser class instead. This will return the Gson object representations (JsonElement) rather than a user defined class, but avoids the problem of conversion to scientific notation.
import java.lang.reflect.Type;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
public class GsonTest
{
public static void main(String[] args)
{
String json = "{numbers:[268627104,485677888,506884800]}";
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> jsonMap = gson.fromJson(json, type);
System.out.println("Gson output:");
System.out.println(jsonMap);
JsonParser jsonParser = new JsonParser();
JsonElement jsonElement = jsonParser.parse(json);
System.out.println("JsonParser output:");
System.out.println(jsonElement);
}
}
Code Output:
Gson output:
{numbers=[2.68627104E8, 4.85677888E8, 5.068848E8]}
JsonParser output:
{"numbers":[268627104,485677888,506884800]}
I had a similar problem, and it not only converts integers to double, but it actually loses precision for certain long numbers, as described in this related question.
I tracked down this conversion to ObjectTypeAdapter's read method, specifically:
case NUMBER:
return in.nextDouble();
It may be possible to plug in a modified TypeAdapter for Object, but I couldn't get that to work, so instead I just copied the read method (Object read(JsonReader in)) to my own code and modified the above lines to this:
case NUMBER:
final String s = in.nextString();
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
// ignore
}
try {
return Long.parseLong(s);
} catch (NumberFormatException e) {
// ignore
}
return Double.parseDouble(s);
I wish Gson did this by default..
Then I put the other connecting pieces in a helper method that looks something like this:
public static Object parse(final Reader r) {
try (final JsonReader jr = new JsonReader(r)) {
jr.setLenient(true);
boolean empty = true;
Object o = null;
try {
jr.peek();
empty = false;
o = read(jr);
} catch (EOFException e) {
if (!empty) {
throw new JsonSyntaxException(e);
}
}
if (o != null && jr.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
return o;
} catch (IOException e) {
throw new JsonIOException(e);
}
}
So now instead of new Gson().fromJson(r, Object.class), I call parse(r).
This works well for me because I want to be able to parse json data with any structure, but if you have a particular class you're targeting, you probably just need to eliminate occurrences of Object within that class's members.
Got the same issue, after some investigation here is what I found.
The behavior:
Gson
For a number without fractional part, Gson would convert it as Double,
Jackson
For a number without fractional part, Jackson would convert it as Integer or Long, depends on how large the number is.
Possible solutions:
Convert Gson's return value from Double to Long, explicitly.
Use Jackson instead.
I prefer this.
Code - test for Jackson
ParseNumberTest.java:
import java.util.List;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* test - jackson parse numbers,
*
* #author eric
* #date Jan 13, 2018 12:28:36 AM
*/
public class ParseNumberTest {
#Test
public void test() throws Exception {
String jsonFn = "numbers.json";
ObjectMapper mapper = new ObjectMapper();
DummyData dd = mapper.readValue(this.getClass().getResourceAsStream(jsonFn), DummyData.class);
for (Object data : dd.dataList) {
System.out.printf("data type: %s, value: %s\n", data.getClass().getName(), data.toString());
Assert.assertTrue(data.getClass() == Double.class || data.getClass() == Long.class || data.getClass() == Integer.class);
System.out.printf("%s\n\n", "------------");
}
}
static class DummyData {
List<Object> dataList;
public List<Object> getDataList() {
return dataList;
}
public void setDataList(List<Object> dataList) {
this.dataList = dataList;
}
}
}
numbers.json:
{
"dataList": [
150000000000,
150778742934,
150000,
150000.0
]
}
How to run:
The test case is based on Jackson & TestNG.
Put numbers.json at the same package as ParseNumberTest.java.
Run as testng test, then it would print type & value of the parse result.
Output:
data type: java.lang.Long, value: 150000000000
------------
data type: java.lang.Long, value: 150778742934
------------
data type: java.lang.Integer, value: 150000
------------
data type: java.lang.Double, value: 150000.0
------------
PASSED: test
Not smart, but still working method is to add " at the start and at the end of the number. Then after processing is finished, delete it.
We can use the below code solution for number Long:
Document doc = documentCursor.next();
JsonWriterSettings relaxed = JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build();
CustomeObject obj = gson.fromJson(doc.toJson(relaxed), CustomeObject.class);
The best solution in case you need them as String was to force attributes to be quoted with a single quote before doing the conversion.
Do changes like this:
String readerAsString = convertReaderToString(reader);
readerAsString = readerAsString.toString().replace("=", "='");
readerAsString = readerAsString.toString().replace(",", "',");
readerAsString = readerAsString.toString().replace("}]", "'}]");
data class Answer(
val question: String,
val value: Any
)
Given the value:Any property above, I find it dubious that Gson encounters a JSON value of 1 (not "1") and cannot INFER the blatantly obvious truth: 1 is an Int. Instead, Gson converts the integer value to the double 1.0.
Gson should only convert a JSON value from 1 to 1.0 if the value property above was of type Float or Double. When Gson encounters a property whose type is Any, it should (quite simply) infer the type from the JSON value it receives. Unfortunately, it doesn't, preferring to actually corrupt incoming integer values by casting them to Double, which unsurprisingly immediately causes exceptions.
I can find no reasonable solution to this peculiarity of the Gson parser. As such, I'm forced to either manually convert all those double values back into int values after using Gson or implement my own generic custom type adapter for Gson. Neither of these options is at all appealing.
I did not find a solution to my problem of gson formatting numbers ending in 0 to scientific notation. I instead used a work-around to convert this scientific notation into a double that I formatted with commas. "value" is the json string.
private String formatNumber(String value) {
double dValue = Double.parseDouble(value);
String pattern = "#,###";
DecimalFormat formatter = new DecimalFormat(pattern);
String newNumber = formatter.format(dValue);
return newNumber;
}
This doesn't answer the question asked but is an added step to work-around the problem to display the numbers as required by the system.

Categories

Resources