Is there a way to ignore JsonSyntaxException in Gson - json

I have a json that looks like this:
[
{
_id: "54b8f62fa08c286b08449b8f",
loc: [
36.860983,
31.0567
]
},
{
_id: "54b8f6aea08c286b08449b93",
loc: {
coordinates: [ ]
}
}
]
As you can see, loc object is sometimes is a json object, sometimes is a double array. Without writing a custom deserializer, is there a way to avoid JsonSyntaxException and set the loc object to null when it is a json object rather than a double array.

There aren't any easy way (I mean a property/method call at Gson) for custom seralization/deserialization of a specific field at a json value.
You can see source code of com.google.gson.internal.bind.ReflectiveTypeAdapterFactory, and debug on its inner class Adapter's read method. (That's where your JsonSyntaxException occurs)
You can read Custom serialization for JUST specific fields and track its links. It may be implemented at future release of Gson. (Not available at latest release 2.2.4)
I would write some code for this. Maybe that's not what you are looking for but it may help somebody else.)
Solution 1 (This has less code compared with the second solution but second solution's performance is much more better):
public class SubClass extends BaseClass {
private double[] loc;
}
public class BaseClass {
#SerializedName("_id")
private String id;
}
public class CustomTypeAdapter extends TypeAdapter<BaseClass> {
private Gson gson;
public CustomTypeAdapter() {
this.gson = new Gson();
}
#Override
public void write(JsonWriter out, BaseClass value)
throws IOException {
throw new RuntimeException("Not implemented for this question!");
}
#Override
public BaseClass read(JsonReader in) throws IOException {
BaseClass instance;
try {
instance = gson.fromJson(in, SubClass.class);
} catch (Exception e) {
e.printStackTrace();
instance = gson.fromJson(in, BaseClass.class);
}
return instance;
}
}
Test:
private void test() {
String json = "[{_id:\"54b8f62fa08c286b08449b8f\",loc:[36.860983,31.0567]},{_id:\"54b8f6aea08c286b08449b93\",loc:{coordinates:[]}}]";
Type collectionType = new TypeToken<List<BaseClass>>(){}.getType();
Gson gson = new GsonBuilder().registerTypeAdapter(BaseClass.class, new CustomTypeAdapter()).create();
List<BaseClass> list = gson.fromJson(json, collectionType);
for(BaseClass item : list) {
if(item instanceof SubClass) {
System.out.println("item has loc value");
SubClass subClassInstance = (SubClass)item;
} else {
System.out.println("item has no loc value");
BaseClass baseClassInstance = item;
}
}
}
Solution 2 (It is one of the Gson Developers suggestion. See original post.):
Copy below class to your project. It is going to be a base class for your custom TypeAdapterFactorys.
public abstract class CustomizedTypeAdapterFactory<C>
implements TypeAdapterFactory {
private final Class<C> customizedClass;
public CustomizedTypeAdapterFactory(Class<C> customizedClass) {
this.customizedClass = customizedClass;
}
#SuppressWarnings("unchecked") // we use a runtime check to guarantee that 'C' and 'T' are equal
public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return type.getRawType() == customizedClass
? (TypeAdapter<T>) customizeMyClassAdapter(gson, (TypeToken<C>) type)
: null;
}
private TypeAdapter<C> customizeMyClassAdapter(Gson gson, TypeToken<C> type) {
final TypeAdapter<C> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<C>() {
#Override public void write(JsonWriter out, C value) throws IOException {
JsonElement tree = delegate.toJsonTree(value);
beforeWrite(value, tree);
elementAdapter.write(out, tree);
}
#Override public C read(JsonReader in) throws IOException {
JsonElement tree = elementAdapter.read(in);
afterRead(tree);
return delegate.fromJsonTree(tree);
}
};
}
/**
* Override this to muck with {#code toSerialize} before it is written to
* the outgoing JSON stream.
*/
protected void beforeWrite(C source, JsonElement toSerialize) {
}
/**
* Override this to muck with {#code deserialized} before it parsed into
* the application type.
*/
protected void afterRead(JsonElement deserialized) {
}
}
Write your POJO and your custom CustomizedTypeAdapterFactory. Override afterRead method and handle double array as you asked at your question:
public class MyClass {
#SerializedName("_id")
private String id;
private double[] loc;
// getters/setters
}
private class MyClassTypeAdapterFactory extends CustomizedTypeAdapterFactory<MyClass> {
private MyClassTypeAdapterFactory() {
super(MyClass.class);
}
#Override protected void afterRead(JsonElement deserialized) {
try {
JsonArray jsonArray = deserialized.getAsJsonObject().get("loc").getAsJsonArray();
System.out.println("loc is not a double array, its ignored!");
} catch (Exception e) {
deserialized.getAsJsonObject().remove("loc");
}
}
}
Test:
private void test() {
String json = "[{_id:\"54b8f62fa08c286b08449b8f\",loc:[36.860983,31.0567]},{_id:\"54b8f6aea08c286b08449b93\",loc:{coordinates:[]}}]";
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new MyClassTypeAdapterFactory())
.create();
Type collectionType = new TypeToken<List<MyClass>>(){}.getType();
List<MyClass> list = gson.fromJson(json, collectionType);
for(MyClass item : list) {
if(item.getLoc() != null) {
System.out.println("item has loc value");
} else {
System.out.println("item has no loc value");
}
}
}

This is how I did this. It is shorter, but I think #DevrimTuncers answer is the best one.
//This is just Double array to use as location object
public class Location extends ArrayList<Double> {
public Double getLatidute() {
if (this.size() > 0) {
return this.get(0);
} else {
return (double) 0;
}
}
public Double getLongitude() {
if (this.size() > 1) {
return this.get(1);
} else {
return (double) 0;
}
}
public static class LocationDeserializer implements JsonDeserializer<Location> {
#Override
public Location deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
JsonArray array = json.getAsJsonArray();
Location location = new Location();
for (int i = 0; i < array.size(); i++) {
location.add(array.get(i).getAsDouble());
}
return location;
} catch (Exception e) {
return null;
}
}
}
}

Related

Is it possible to make Jackson serialize a nested object as a string

Given these classes:
#Value
private static class Message {
private final String type;
private final MyType message;
}
#Value
public class MyType {
private final String foo;
}
Jackson will produce:
{
"Type" : "Test",
"Message" : {"foo" : "bar"}
}
Is there some type of annotation or instruction I can give to Jackson to ask it to serialize the nested complex type as a string, e.g. the desired JSON would be:
{
"Type" : "Test",
"Message" : "{\"foo\" : \"bar\"}"
}
I tried both of these annotations on the message field:
#JsonFormat(shape = JsonFormat.Shape.STRING)
#JsonSerialize(as=String.class)
Neither has the desired impact. For now my "hack" is to do this at construction time:
return new Message("Test", mapper.writeValueAsString(new MyType("bar")));
I guess I could write a custom serializer, but I wondered if this is some type of standard behaviour that is built in. My use case is that I'm constructing a JSON payload which is expected to have a string message contained within it that itself contains JSON.
Environment
Jackson version is 2.9.0 using Spring Boot 2 on Java 10.
It can be done with custom serializer:
class EscapedJsonSerializer extends StdSerializer<Object> {
public EscapedJsonSerializer() {
super((Class<Object>) null);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
StringWriter str = new StringWriter();
JsonGenerator tempGen = new JsonFactory().setCodec(gen.getCodec()).createGenerator(str);
if (value instanceof Collection || value.getClass().isArray()) {
tempGen.writeStartArray();
if (value instanceof Collection) {
for (Object it : (Collection) value) {
writeTree(gen, it, tempGen);
}
} else if (value.getClass().isArray()) {
for (Object it : (Object[]) value) {
writeTree(gen, it, tempGen);
}
}
tempGen.writeEndArray();
} else {
provider.defaultSerializeValue(value, tempGen);
}
tempGen.flush();
gen.writeString(str.toString());
}
#Override
public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
StringWriter str = new StringWriter();
JsonGenerator tempGen = new JsonFactory().setCodec(gen.getCodec()).createGenerator(str);
writeTree(gen, value, tempGen);
tempGen.flush();
gen.writeString(str.toString());
}
private void writeTree(JsonGenerator gen, Object it, JsonGenerator tempGen) throws IOException {
ObjectNode tree = ((ObjectMapper) gen.getCodec()).valueToTree(it);
tree.set("#class", new TextNode(it.getClass().getName()));
tempGen.writeTree(tree);
}
}
and deserializer:
class EscapedJsonDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer {
private final Map<JavaType, JsonDeserializer<Object>> cachedDeserializers = new HashMap<>();
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
throw new IllegalArgumentException("EscapedJsonDeserializer should delegate deserialization for concrete class");
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
JavaType type = (ctxt.getContextualType() != null) ?
ctxt.getContextualType() : property.getMember().getType();
return cachedDeserializers.computeIfAbsent(type, (a) -> new InnerDeserializer(type));
}
private class InnerDeserializer extends JsonDeserializer<Object> {
private final JavaType javaType;
private InnerDeserializer(JavaType javaType) {
this.javaType = javaType;
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String string = p.readValueAs(String.class);
return ((ObjectMapper) p.getCodec()).readValue(string, javaType);
}
#Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
throws IOException {
String str = p.readValueAs(String.class);
TreeNode root = ((ObjectMapper) p.getCodec()).readTree(str);
Class clz;
try {
clz = Class.forName(((TextNode) root.get("#class")).asText());
Object newJsonNode = p.getCodec().treeToValue(root, clz);
return newJsonNode;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
The field should be annotated with #JsonSerialize and #JsonDeserialize (if needed)
class Outer {
#JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.CLASS)
#JsonSerialize(using = EscapedJsonSerializer.class)
#JsonDeserialize(using = EscapedJsonDeserializer.class)
public Foo val;
}
It works well with simple collections (list, arrays) and to some extent with polymorphism, although more elaborate solution may be needed for specific polymorphism related issues.
Example output looks like this:
{"val":"{\"foo\":\"foo\",\"#class\":\"org.test.Foo\"}"}
{"val":"{\"foo\":\"foo\",\"bar\":\"bar\",\"#class\":\"org.test.Bar\"}"}
I also couldn't find built-in solution and ended up writing custom converter:
public class ObjectToJsonStringConverter extends StdConverter<Object, String> {
private final ObjectMapper objectMapper = new ObjectMapper();
#Override
public String convert(Object value) {
try {
return objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
}
}
usage:
#Value
private static class Message {
private final String type;
#JsonSerialize(converter = ObjectToJsonStringConverter.class)
private final MyType message;
}

org.codehaus.jackson.JsonGenerationException: Can not write number, expecting field name

Hi i am working on a spring mvc application well i need to Serialize an object in order to pass it with an ajax Post.
my bean class :
#JsonSerialize(using = AgentSer.class)
public class AgentCust implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Long personneID;
private String nom;
private String prenom;
private String matriculation;
private String marche;
private String compte;
private String phone, mail, chat;
public String getMarche() {
return marche;
}
public void setMarche(String marche) {
this.marche = marche;
}
public String getCompte() {
return compte;
}
public void setCompte(String compte) {
this.compte = compte;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public String getChat() {
return chat;
}
public void setChat(String chat) {
this.chat = chat;
}
public Long getPersonneID() {
return personneID;
}
public void setPersonneID(Long personneID) {
this.personneID = personneID;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public String getMatriculation() {
return matriculation;
}
public void setMatriculation(String matriculation) {
this.matriculation = matriculation;
}
}
and the class that will serialize my bean :
public class AgentSer extends JsonSerializer<AgentCust> {
#Override
public void serialize(AgentCust value, JsonGenerator jgen, SerializerProvider arg2) throws IOException, JsonProcessingException {
// TODO Auto-generated method stub
jgen.writeStartObject();
jgen.writeNumber(value.getPersonneID());
jgen.writeString(value.getMatriculation());
jgen.writeString(value.getNom());
jgen.writeString(value.getPrenom());
jgen.writeString(value.getCompte());
jgen.writeString(value.getMarche());
jgen.writeString(value.getChat());
jgen.writeString(value.getMail());
jgen.writeString(value.getPhone());
jgen.writeEndObject();
}
}
in my controller i use my class like that:
AgentCust ags ;
// i set values here .
ObjectMapper mapper = new ObjectMapper();
String json = "";
try {
json = mapper.writeValueAsString(ags);
} catch (Exception e) {
System.out.println(e);
}
but at the end i get that :
org.codehaus.jackson.JsonGenerationException: Can not write number, expecting field name
any help please.
Why are you using a custom serializer(which is wrong as it doesn't include the field names). You are really complicating your life.
You can set the serialization options like this (you can also set them in a static block):
final ObjectMapper mapper = new ObjectMapper();
/*
you can set them globally in a static block and reuse the mapper...
performance gain
*/
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false);
mapper.setSerializationInclusion(Include.NON_NULL);
The rest of the code is the same(just add a constructor in your AgentCust.class to avoid some mapping errors):
AgentCust ags = new AgentCust();
ags.setChat("chat1");
ags.setCompte("compte1");
ags.setMail("mail1");
ags.setMarche("marche1");
ags.setMatriculation("matriculation1");
ags.setNom("nom1");
ags.setPersonneID(123456L);
ags.setPhone("phone1");
ags.setPrenom("prenom1");
String json = "";
try {
json = mapper.writeValueAsString(ags);
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(json);
Another strange thing is that you're serializing the pojo as String. Why not JsonNode or ObjectNode?
public static ObjectNode convObjToONode(Object o) {
StringWriter stringify = new StringWriter();
ObjectNode objToONode = null;
try {
mapper.writeValue(stringify, o);
objToONode = (ObjectNode) mapper.readTree(stringify.toString());
} catch (JsonMappingException e) {
Logger.error("ERROR MAPPING JSON from object!", e);
} catch (JsonGenerationException e) {
Logger.error("ERROR GENERATING JSON from object!", e);
} catch (IOException e) {
Logger.error("ERROR IO when writing JSON from object!", e);
}
Logger.debug("Object as ObjectNode : " + objToONode);
return objToONode;
}

Simple way to strip outer array of responses in gson

I'm working with an api (Phillips Hue) that wraps all of it's json responses in an array with one entry (the content).
Example:
[{
"error": {
"type": 5,
"address": "/",
"description": "invalid/missing parameters in body"
}
}]
I usually write standard POJO's parsed by GSON to handle responses but since the response is not a json object I'm a bit stumped on the best way to deal with this. I didn't really want every object to actually be an array that I have to call .get(0) on.
Example of the POJO if it was a JSON obj and NOT wrapped in an array.
public class DeviceUserResponse {
private DeviceUser success;
private Error error;
public DeviceUser getSuccess() {
return success;
}
public Error getError() {
return error;
}
public static class Error {
private int type;
private String address;
private String description;
public int getType() {
return type;
}
public String getAddress() {
return address;
}
public String getDescription() {
return description;
}
#Override
public String toString() {
return "Type: " + this.type
+ " Address: " + this.address
+ " Description: " + this.description;
}
}
}
What I have to do right now:
ArrayList<DeviceUserResponse> response.get(0).getError();
Is there a way that I can strip this array for every response or am I just going to have to do a .get(0) in my POJO's and just not expose it?
I think you've to go with custom deserialization in order to "strip out" the array.
Here a possible solution.
An adapter for your response POJO:
public class DeviceUserResponseAdapter extends TypeAdapter<DeviceUserResponse> {
protected TypeAdapter<DeviceUserResponse> defaultAdapter;
public DeviceUserResponseAdapter(TypeAdapter<DeviceUserResponse> defaultAdapter) {
this.defaultAdapter = defaultAdapter;
}
#Override
public void write(JsonWriter out, DeviceUserResponse value) throws IOException {
defaultAdapter.write(out, value);
}
#Override
public DeviceUserResponse read(JsonReader in) throws IOException {
in.beginArray();
assert(in.hasNext());
DeviceUserResponse response = defaultAdapter.read(in);
in.endArray();
return response;
}
}
A factory for your adapter:
public class DeviceUserResponseAdapterFactory implements TypeAdapterFactory {
#Override
#SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType()!=DeviceUserResponse.class) return null;
TypeAdapter<DeviceUserResponse> defaultAdapter = (TypeAdapter<DeviceUserResponse>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new DeviceUserResponseAdapter(defaultAdapter);
}
}
Then you've to register and user it:
DeviceUserResponseAdapterFactory adapterFactory = new DeviceUserResponseAdapterFactory();
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.registerTypeAdapterFactory(adapterFactory).create();
DeviceUserResponse response = gson.fromJson(json, DeviceUserResponse.class);
System.out.println(response.getError());
This solution will not work if you have the DeviceUserResponse inside other complex JSON object. I that case the adapter will try to find an array and will terminate with an error.
Another solution is to parse it as array and then in your "communication" layer you get only the first element. This will preserve the GSon deserialization.
In the comment you're asking for a more generic solution, here one:
The adapter:
public class ResponseAdapter<T> extends TypeAdapter<T> {
protected TypeAdapter<T> defaultAdapter;
public ResponseAdapter(TypeAdapter<T> defaultAdapter) {
this.defaultAdapter = defaultAdapter;
}
#Override
public void write(JsonWriter out, T value) throws IOException {
defaultAdapter.write(out, value);
}
#Override
public T read(JsonReader in) throws IOException {
in.beginArray();
assert(in.hasNext());
T response = defaultAdapter.read(in);
in.endArray();
return response;
}
}
The factory:
public class ResponseAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if ((type.getRawType().getSuperclass() != Response.class)) return null;
TypeAdapter<T> defaultAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new ResponseAdapter<T>(defaultAdapter);
}
}
Where Response.class is your super class from which all the service responses inherit.
The first solution advices are still valid.

Json.Net CustomSerialization

I am serializing a collection of objects that contains a dictionary called dynamic properties.
The default Json emitted looks like this:
[{"dynamicProperties":{"WatchId":7771,"Issues":0,"WatchType":"x","Location":"Equinix Source","Name":"PI_5570_5580"}},
{"dynamicProperties":{"WatchId":7769,"Issues":0,"WatchType":"x","Location":"Equinix Source","Name":"PI_5570_5574"}},
{"dynamicProperties":{"WatchId":7767,"Issues":0,"WatchType":"x","Location":"Equinix Source","Name":"PI_5570_5572"}},
{"dynamicProperties":{"WatchId":7765,"Issues":0,"WatchType":"y","Location":"Equinix Source","Name":"highlight_SM"}},
{"dynamicProperties":{"WatchId":8432,"Issues":0,"WatchType":"y","Location":"Test Devices","Name":"Cisco1700PI"}}]
I'd like to produce Json that looks like this:
[{"WatchId":7771,"Issues":0,"WatchType":"x","Location":"Equinix Source","Name":"PI_5570_5580"},
{"WatchId":7769,"Issues":0,"WatchType":"x","Location":"Equinix Source","Name":"PI_5570_5574"},
{"WatchId":7767,"Issues":0,"WatchType":"x","Location":"Equinix Source","Name":"PI_5570_5572"},
{"WatchId":7765,"Issues":0,"WatchType":"y","Location":"Equinix Source","Name":"highlight_SM"},
{"WatchId":8432,"Issues":0,"WatchType":"y","Location":"Test Devices","Name":"Cisco1700PI"}]
From reading the Json.Net documentation it looks like I could build a CustomContractResolver for my class, but I cannot find any details on how to go about this... Can anyone shed any light on the direction I should be looking in?
The class I am trying to serialize is below.
I needed a class that allow dynamic style behaviour and is also serializable and deserializable over WCF.
[DataContract]
public class SerializableDynamicObject : IDynamicMetaObjectProvider
{
[DataMember]
private IDictionary<string, object> dynamicProperties = new Dictionary<string, object>();
#region IDynamicMetaObjectProvider implementation
public DynamicMetaObject GetMetaObject(Expression expression)
{
return new SerializableDynamicMetaObject(expression,
BindingRestrictions.GetInstanceRestriction(expression, this), this);
}
#endregion
#region Helper methods for dynamic meta object support
internal object setValue(string name, object value)
{
dynamicProperties.Add(name, value);
return value;
}
internal object getValue(string name)
{
object value;
if (!dynamicProperties.TryGetValue(name, out value))
{
value = null;
}
return value;
}
internal IEnumerable<string> getDynamicMemberNames()
{
return dynamicProperties.Keys;
}
#endregion
}
Implement ISerializable
[DataContract]
public class SerializableDynamicObject : IDynamicMetaObjectProvider, ISerializable
{
[DataMember]
private IDictionary<string, object> dynamicProperties = new Dictionary<string, object>();
#region IDynamicMetaObjectProvider implementation
public DynamicMetaObject GetMetaObject(Expression expression)
{
return new SerializableDynamicMetaObject(expression,
BindingRestrictions.GetInstanceRestriction(expression, this), this);
}
#endregion
#region Helper methods for dynamic meta object support
internal object setValue(string name, object value)
{
dynamicProperties.Add(name, value);
return value;
}
internal object getValue(string name)
{
object value;
if (!dynamicProperties.TryGetValue(name, out value))
{
value = null;
}
return value;
}
internal IEnumerable<string> getDynamicMemberNames()
{
return dynamicProperties.Keys;
}
#endregion
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
foreach (var key in dynamicProperties.Keys)
{
info.AddValue(key.ToString(), dynamicProperties[key]);
}
}
#endregion
}
Now test this code in a little console app...
static void Main(string[] args)
{
SerializableDynamicObject obj1 = new SerializableDynamicObject();
obj1.setValue("WatchId", 7771);
obj1.setValue("Issues", 0);
obj1.setValue("Location", "sample location1");
obj1.setValue("Name", "sample name 1");
SerializableDynamicObject obj2 = new SerializableDynamicObject();
obj2.setValue("WatchId", 7771);
obj2.setValue("Issues", 0);
obj2.setValue("Location", "sample location1");
obj2.setValue("Name", "sample name 1");
SerializableDynamicObject obj3 = new SerializableDynamicObject();
obj3.setValue("WatchId", 7771);
obj3.setValue("Issues", 0);
obj3.setValue("Location", "sample location1");
obj3.setValue("Name", "sample name 1");
SerializableDynamicObject[] dictArray = new
SerializableDynamicObject[] {
obj1, obj2, obj3
};
Newtonsoft.Json.JsonSerializer ser = new Newtonsoft.Json.JsonSerializer();
ser.Serialize(Console.Out, dictArray);
}
This is the output of the program:
[{"WatchId":7771,"Issues":0,"Location":"sample location1","Name":"sample name 1"},{"WatchId":7771,"Issues":0,"Location":"sample location2","Name":"sample name 2"},{"WatchId":7771,"Issues":0,"Location":"sample location3","Name":"sample name3"}]

Jackson readValue mapping to overloaded java class problem

I have some JSON where one of the keys has one of three values: an int, a string, or a json object. Using the snippet below I can map this field when it is an int or a string but fail when it's a json object. Where am I going wrong? What should I be doing?
The JSON value key looks like:
"value": 51,
or
"value": 51,
or (and this is where I am having trouble)
"value": {"lat": 53.990614999999998, "lng": -1.5391117000000301, "addr": "Harrogate, North Yorkshire, UK"}
public class Test {
public Test() {
}
public static class Value {
public int slidervalue;
public String voicevalue;
public GeoValue geovalue; // problem
public Value(int value) {
this.slidervalue = value
}
public Value(String value) {
this.voicevalue = value;
}
public Value(JSONObject value) {
JSONObject foo = value; // this is never reached
this.geovalue = value; // and how would this work so as map value to a GeoValue?
}
private static class GeoValue {
private double _lat;
private double _lng;
private String _addr;
public float getLat() {
return (float)_lat;
}
public void setLat(float lat) {
_lat = (double)lat;
}
public float getLng() { return (float)_lng;}
public void setLng(float lng) { _lng = (double)lng; }
public String getAddr() { return _addr;}
public void setAddr(String addr) { _addr = addr; }
}
} // end of Value class
public Value getValue() { return _value;}
public void setValue(Value value) {
_value = value;
}
} //end of Test class
and this is being used like this:
ObjectMapper mapper = new ObjectMapper();
instance = mInstances.getJSONObject(i).toString();
Test testinstance = mapper.readValue(instance, Test.class);
public class Test {
public Test() {
}
public static class Value {
public int slidervalue;
public String voicevalue;
public GeoValue geovalue; // problem
public Value(int value) {
this.slidervalue = value
}
public Value(String value) {
this.voicevalue = value;
}
public Value(JSONObject value) {
JSONObject foo = value; // this is never reached
this.geovalue = value; // and how would this work so as map value to a GeoValue?
}
private static class GeoValue {
private double _lat;
private double _lng;
private String _addr;
public float getLat() {
return (float)_lat;
}
public void setLat(float lat) {
_lat = (double)lat;
}
public float getLng() { return (float)_lng;}
public void setLng(float lng) { _lng = (double)lng; }
public String getAddr() { return _addr;}
public void setAddr(String addr) { _addr = addr; }
}
} // end of Value class
public Value getValue() { return _value;}
public void setValue(Value value) {
_value = value;
}
} //end of Test class
and this is being used like this:
ObjectMapper mapper = new ObjectMapper();
instance = mInstances.getJSONObject(i).toString();
Test testinstance = mapper.readValue(instance, Test.class);
This fails with a JSONMappingException: No suitable contructor found for type ... 'value'
Thanks. Alex
What might work is that you mark the constructor that takes JSONObject with #JsonCreator, but do NOT add #JsonProperty for the single parameter. In that case, incoming JSON is bound to type of that parameter (in this case JSONObject, but you could use Map as well), and passed to constructor.
Overloading still works because of special handling for single-string/int/long-argument constructor.
I am not sure if that is the cleanest solution; it might be cleanest to just implement custom deserializer. But it should work.
If your code is what you want, your json should be like this:
{"value":{"slidervalue":1,"voicevalue":"aa","geovalue":{"lat":53.990615,"lng":-1.53911170000003,"addr":"Harrogate, North Yorkshire, UK"}}}