I have an app in .NET that I want to be able to talk to an Android app. Some research suggested not using XML, my usual choice, but instead using JSON which I've never touched. I'm using Json.NET and GSON.
I have a simple class in .NET;
public enum eCommandType
{
UNKNOWN,
PING,
GET_TIMINGS,
GET_EVENT_ID,
NEW_EVENT,
LOAD_PEOPLE,
LOAD_TAGS,
SET_LOCATION
}
public class CommandBase
{
[JsonConverter(typeof(StringEnumConverter))]
public eCommandType CommandType { get; set; }
}
I generate a schema using
//Encode enums as strings, not integers.
JSchemaGenerator stringEnumGenerator = new JSchemaGenerator();
stringEnumGenerator.GenerationProviders.Add
(new StringEnumGenerationProvider());
JSchema schema = stringEnumGenerator.Generate(typeof(CommandBase));
string s = schema.ToString();
That schema is
{
"type": "object",
"properties": {
"CommandType": {
"type": "string",
"enum": [
"UNKNOWN",
"PING",
"GET_TIMINGS",
"GET_EVENT_ID",
"NEW_EVENT",
"LOAD_PEOPLE",
"LOAD_TAGS",
"SET_LOCATION"
]
}
},
"required": [
"CommandType"
]
}
I then go to jsonschema2pojo and create a POJO, selecting "Gson" as the annotation style since it seems the thing to do. I press Preview and get a class that doesn't contain those enum values;
#Generated("org.jsonschema2pojo")
public class CommandType {
#Expose
private String type;
#SerializedName("enum")
#Expose
private List<String> _enum = new ArrayList<String>();
/**
*
* #return
* The type
*/
public String getType() {
return type;
}
/**
*
* #param type
* The type
*/
public void setType(String type) {
this.type = type;
}
/**
*
* #return
* The _enum
*/
public List<String> getEnum() {
return _enum;
}
/**
*
* #param _enum
* The enum
*/
public void setEnum(List<String> _enum) {
this._enum = _enum;
}
}
Unsurprisingly, if I go on to try and deserialize something using this in an Android app, I get an object that just has default values for the fields. Since there's obvious problems at this stage I've not included details of the Android side.
Aside from the missing enum values, there's no property in the POJO called "CommandType." The class itself has taken that name, the original name being lost in schema generation.
If I try the generated schema against jsonschemavalidator I get the error
Found 1 error(s)
Message: Required properties are missing from object: CommandType.
Schema path: #/required
So it seems I'm using Json.NET wrongly. However my usage seems pretty simple; I want enum strings rather than an int to make the eventual protocol robust as things change, and I want a type indication so I can deserialise to the correct POJO on the Android side, since I'll be having several different classes for a multitype query/response protocol. Is there something obviously wrong in my attempt to do this?
Select JSON Schema rather than JSON as your Source type at http://www.jsonschema2pojo.org/
Results:
#Generated("org.jsonschema2pojo")
public static enum CommandType {
UNKNOWN("UNKNOWN"),
PING("PING"),
GET_TIMINGS("GET_TIMINGS"),
GET_EVENT_ID("GET_EVENT_ID"),
NEW_EVENT("NEW_EVENT"),
LOAD_PEOPLE("LOAD_PEOPLE"),
LOAD_TAGS("LOAD_TAGS"),
SET_LOCATION("SET_LOCATION");
private final String value;
...etc
Related
How should one deal with Gsonand required versus optional fields?
Since all fields are optional, I can't really fail my network request based on if the response json contains some key, Gsonwill simply parse it to null.
Method I am using gson.fromJson(json, mClassOfT);
For example if I have following json:
{"user_id":128591, "user_name":"TestUser"}
And my class:
public class User {
#SerializedName("user_id")
private String mId;
#SerializedName("user_name")
private String mName;
public String getId() {
return mId;
}
public void setId(String id) {
mId = id;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
}
Is the any option to get Gson to fail if json would not contain user_id or user_name key?
There can be many cases where you might need at least some values to be parsed and other one could be optional?
Is there any pattern or library to be used to handle this case globally?
Thanks.
As you note, Gson has no facility to define a "required field" and you'll just get null in your deserialized object if something is missing in the JSON.
Here's a re-usable deserializer and annotation that will do this. The limitation is that if the POJO required a custom deserializer as-is, you'd have to go a little further and either pass in a Gson object in the constructor to deserialize to object itself or move the annotation checking out into a separate method and use it in your deserializer. You could also improve on the exception handling by creating your own exception and pass it to the JsonParseException so it can be detected via getCause() in the caller.
That all said, in the vast majority of cases, this will work:
public class App
{
public static void main(String[] args)
{
Gson gson =
new GsonBuilder()
.registerTypeAdapter(TestAnnotationBean.class, new AnnotatedDeserializer<TestAnnotationBean>())
.create();
String json = "{\"foo\":\"This is foo\",\"bar\":\"this is bar\"}";
TestAnnotationBean tab = gson.fromJson(json, TestAnnotationBean.class);
System.out.println(tab.foo);
System.out.println(tab.bar);
json = "{\"foo\":\"This is foo\"}";
tab = gson.fromJson(json, TestAnnotationBean.class);
System.out.println(tab.foo);
System.out.println(tab.bar);
json = "{\"bar\":\"This is bar\"}";
tab = gson.fromJson(json, TestAnnotationBean.class);
System.out.println(tab.foo);
System.out.println(tab.bar);
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface JsonRequired
{
}
class TestAnnotationBean
{
#JsonRequired public String foo;
public String bar;
}
class AnnotatedDeserializer<T> implements JsonDeserializer<T>
{
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException
{
T pojo = new Gson().fromJson(je, type);
Field[] fields = pojo.getClass().getDeclaredFields();
for (Field f : fields)
{
if (f.getAnnotation(JsonRequired.class) != null)
{
try
{
f.setAccessible(true);
if (f.get(pojo) == null)
{
throw new JsonParseException("Missing field in JSON: " + f.getName());
}
}
catch (IllegalArgumentException ex)
{
Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
}
catch (IllegalAccessException ex)
{
Logger.getLogger(AnnotatedDeserializer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return pojo;
}
}
Output:
This is foo
this is bar
This is foo
null
Exception in thread "main" com.google.gson.JsonParseException: Missing field in JSON: foo
Answer of Brian Roach is very good, but sometimes it's also necessary to handle:
properties of model's super class
properties inside of arrays
For these purposes the following class can be used:
/**
* Adds the feature to use required fields in models.
*
* #param <T> Model to parse to.
*/
public class JsonDeserializerWithOptions<T> implements JsonDeserializer<T> {
/**
* To mark required fields of the model:
* json parsing will be failed if these fields won't be provided.
* */
#Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
#Target(ElementType.FIELD) // to make annotation accessible through reflection
public #interface FieldRequired {}
/**
* Called when the model is being parsed.
*
* #param je Source json string.
* #param type Object's model.
* #param jdc Unused in this case.
*
* #return Parsed object.
*
* #throws JsonParseException When parsing is impossible.
* */
#Override
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
// Parsing object as usual.
T pojo = new Gson().fromJson(je, type);
// Getting all fields of the class and checking if all required ones were provided.
checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);
// Checking if all required fields of parent classes were provided.
checkSuperClasses(pojo);
// All checks are ok.
return pojo;
}
/**
* Checks whether all required fields were provided in the class.
*
* #param fields Fields to be checked.
* #param pojo Instance to check fields in.
*
* #throws JsonParseException When some required field was not met.
* */
private void checkRequiredFields(#NonNull Field[] fields, #NonNull Object pojo)
throws JsonParseException {
// Checking nested list items too.
if (pojo instanceof List) {
final List pojoList = (List) pojo;
for (final Object pojoListPojo : pojoList) {
checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
checkSuperClasses(pojoListPojo);
}
}
for (Field f : fields) {
// If some field has required annotation.
if (f.getAnnotation(FieldRequired.class) != null) {
try {
// Trying to read this field's value and check that it truly has value.
f.setAccessible(true);
Object fieldObject = f.get(pojo);
if (fieldObject == null) {
// Required value is null - throwing error.
throw new JsonParseException(String.format("%1$s -> %2$s",
pojo.getClass().getSimpleName(),
f.getName()));
} else {
checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
checkSuperClasses(fieldObject);
}
}
// Exceptions while reflection.
catch (IllegalArgumentException | IllegalAccessException e) {
throw new JsonParseException(e);
}
}
}
}
/**
* Checks whether all super classes have all required fields.
*
* #param pojo Object to check required fields in its superclasses.
*
* #throws JsonParseException When some required field was not met.
* */
private void checkSuperClasses(#NonNull Object pojo) throws JsonParseException {
Class<?> superclass = pojo.getClass();
while ((superclass = superclass.getSuperclass()) != null) {
checkRequiredFields(superclass.getDeclaredFields(), pojo);
}
}
}
First of all the interface (annotation) to mark required fields with is described, we'll see an example of its usage later:
/**
* To mark required fields of the model:
* json parsing will be failed if these fields won't be provided.
* */
#Retention(RetentionPolicy.RUNTIME) // to make reading of this field possible at the runtime
#Target(ElementType.FIELD) // to make annotation accessible throw the reflection
public #interface FieldRequired {}
Then deserialize method is implemented. It parses json strings as usual: missing properties in result pojo will have null values:
T pojo = new Gson().fromJson(je, type);
Then the recursive check of all fields of the parsed pojo is being launched:
checkRequiredFields(pojo.getClass().getDeclaredFields(), pojo);
Then we also check all fields of pojo's super classes:
checkSuperClasses(pojo);
It's required when some SimpleModel extends its SimpleParentModel and we want to make sure that all properties of SimpleModel marked as required are provided as SimpleParentModel's ones.
Let's take a look on checkRequiredFields method. First of all it checks if some property is instance of List (json array) - in this case all objects of the list should also be checked to make sure that they have all required fields provided too:
if (pojo instanceof List) {
final List pojoList = (List) pojo;
for (final Object pojoListPojo : pojoList) {
checkRequiredFields(pojoListPojo.getClass().getDeclaredFields(), pojoListPojo);
checkSuperClasses(pojoListPojo);
}
}
Then we are iterating through all fields of pojo, checking if all fields with FieldRequired annotation are provided (what means these fields are not null). If we have encountered some null property which is required - an exception will be fired. Otherwise another recursive step of the validation will be launched for current field, and properties of parent classes of the field will be checked too:
for (Field f : fields) {
// If some field has required annotation.
if (f.getAnnotation(FieldRequired.class) != null) {
try {
// Trying to read this field's value and check that it truly has value.
f.setAccessible(true);
Object fieldObject = f.get(pojo);
if (fieldObject == null) {
// Required value is null - throwing error.
throw new JsonParseException(String.format("%1$s -> %2$s",
pojo.getClass().getSimpleName(),
f.getName()));
} else {
checkRequiredFields(fieldObject.getClass().getDeclaredFields(), fieldObject);
checkSuperClasses(fieldObject);
}
}
// Exceptions while reflection.
catch (IllegalArgumentException | IllegalAccessException e) {
throw new JsonParseException(e);
}
}
}
And the last method should be reviewed is checkSuperClasses: it just runs the similar required fields validation checking properties of pojo's super classes:
Class<?> superclass = pojo.getClass();
while ((superclass = superclass.getSuperclass()) != null) {
checkRequiredFields(superclass.getDeclaredFields(), pojo);
}
And finally lets review some example of this JsonDeserializerWithOptions's usage. Assume we have the following models:
private class SimpleModel extends SimpleParentModel {
#JsonDeserializerWithOptions.FieldRequired Long id;
#JsonDeserializerWithOptions.FieldRequired NestedModel nested;
#JsonDeserializerWithOptions.FieldRequired ArrayList<ListModel> list;
}
private class SimpleParentModel {
#JsonDeserializerWithOptions.FieldRequired Integer rev;
}
private class NestedModel extends NestedParentModel {
#JsonDeserializerWithOptions.FieldRequired Long id;
}
private class NestedParentModel {
#JsonDeserializerWithOptions.FieldRequired Integer rev;
}
private class ListModel {
#JsonDeserializerWithOptions.FieldRequired Long id;
}
We can be sure that SimpleModel will be parsed correctly without exceptions in this way:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(SimpleModel.class, new JsonDeserializerWithOptions<SimpleModel>())
.create();
gson.fromJson("{\"list\":[ { \"id\":1 } ], \"id\":1, \"rev\":22, \"nested\": { \"id\":2, \"rev\":2 }}", SimpleModel.class);
Of course, provided solution can be improved and accept more features: for example - validations for nested objects which are not marked with FieldRequired annotation. Currently it's out of answer's scope, but can be added later.
(Inspired by Brian Roache's answer.)
It seems that Brian's answer doesn't work for primitives because the values can be initialized as something other than null (e.g. 0).
Moreover, it seems like the deserializer would have to be registered for every type. A more scalable solution uses TypeAdapterFactory (as below).
In certain circumstances, it is safer to whitelist exceptions from required fields (i.e. as JsonOptional fields) rather than annotating all fields as required.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface JsonOptional {
}
Though this approach can easily be adapted for required fields instead.
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class AnnotatedTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
Set<Field> requiredFields = Stream.of(rawType.getDeclaredFields())
.filter(f -> f.getAnnotation(JsonOptional.class) == null)
.collect(Collectors.toSet());
if (requiredFields.isEmpty()) {
return null;
}
final TypeAdapter<T> baseAdapter = (TypeAdapter<T>) gson.getAdapter(rawType);
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter jsonWriter, T o) throws IOException {
baseAdapter.write(jsonWriter, o);
}
#Override
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = Streams.parse(in);
if (jsonElement.isJsonObject()) {
ArrayList<String> missingFields = new ArrayList<>();
for (Field field : requiredFields) {
if (!jsonElement.getAsJsonObject().has(field.getName())) {
missingFields.add(field.getName());
}
}
if (!missingFields.isEmpty()) {
throw new JsonParseException(
String.format("Missing required fields %s for %s",
missingFields, rawType.getName()));
}
}
TypeAdapter<T> delegate = gson.getDelegateAdapter(AnnotatedTypeAdapterFactory.this, typeToken);
return delegate.fromJsonTree(jsonElement);
}
};
}
}
This is my simple solution that creates a generic solution with minimum coding.
Create #Optional annotation
Mark First Optional. Rest are assumed optional. Earlier are assumed required.
Create a generic 'loader' method that checks that source Json object has a value. The loop stops once an #Optional field is encountered.
I am using subclassing so the grunt work is done in the superclass.
Here is the superclass code.
import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
...
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Optional {
public boolean enabled() default true;
}
and the grunt work method
#SuppressWarnings ("unchecked")
public <T> T payload(JsonObject oJR,Class<T> T) throws Exception {
StringBuilder oSB = new StringBuilder();
String sSep = "";
Object o = gson.fromJson(oJR,T);
// Ensure all fields are populated until we reach #Optional
Field[] oFlds = T.getDeclaredFields();
for(Field oFld:oFlds) {
Annotation oAnno = oFld.getAnnotation(Optional.class);
if (oAnno != null) break;
if (!oJR.has(oFld.getName())) {
oSB.append(sSep+oFld.getName());
sSep = ",";
}
}
if (oSB.length() > 0) throw CVT.e("Required fields "+oSB+" mising");
return (T)o;
}
and an example of usage
public static class Payload {
String sUserType ;
String sUserID ;
String sSecpw ;
#Optional
String sUserDev ;
String sUserMark ;
}
and the populating code
Payload oPL = payload(oJR,Payload.class);
In this case sUserDev and sUserMark are optional and the rest required. The solution relies on the fact that the class stores the Field definitions in the declared order.
I searched a lot and found no good answer. The solution I chose is as follows:
Every field that I need to set from JSON is an object, i.e. boxed Integer, Boolean, etc. Then, using reflection, I can check that the field is not null:
public class CJSONSerializable {
public void checkDeserialization() throws IllegalAccessException, JsonParseException {
for (Field f : getClass().getDeclaredFields()) {
if (f.get(this) == null) {
throw new JsonParseException("Field " + f.getName() + " was not initialized.");
}
}
}
}
From this class, I can derive my JSON object:
public class CJSONResp extends CJSONSerializable {
#SerializedName("Status")
public String status;
#SerializedName("Content-Type")
public String contentType;
}
and then after parsing with GSON, I can call checkDeserialization and it will report me if some of the fields is null.
I am using JAX-RS (CXF) with JaxB and Jackson to provide a REST-API.
Unfortunately, none of the found results helps me with following (simple) problem:
I implemented following method:
#POST
#Path(ApiStatics.ARMY_CREATE_ARMY)
public com.empires.web.dto.Army createArmy(#FormParam("locationid") long locationId, #FormParam("name") String name, #FormParam("troops") ArmyTroops troops) {
and here are is my model class:
#XmlRootElement
#XmlSeeAlso(ArmyTroop.class)
public class ArmyTroops {
public ArmyTroops() {
}
public ArmyTroops(List<ArmyTroop> troops) {
this.troops = troops;
}
#XmlElement(name = "troops")
private List<ArmyTroop> troops = new ArrayList<ArmyTroop>();
public List<ArmyTroop> getTroops() {
return troops;
}
public void setTroops(List<ArmyTroop> troops) {
this.troops = troops;
}
}
ArmyTroop
#XmlRootElement(name = "troops")
public class ArmyTroop {
#XmlElement
private long troopId;
#XmlElement
private String amount;
public long getTroopId() {
return troopId;
}
public void setTroopId(long troopId) {
this.troopId = troopId;
}
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
}
My json that i send looks like this:
locationid 1
name asdasd
troops {"troops":[{"troopId":4,"amount":"5"},{"troopId":6,"amount":"5"}]}
Unfortunately, the object gets not transformed. Instead I receive this error:
InjectionUtils #reportServerError - Parameter Class com.empires.web.dto.in.ArmyTroops has no constructor with single String parameter, static valueOf(String) or fromString(String) methods
If I provide the constructor with a single string parameter, I get passed the whole json string for "troops" as mentioned above.
Any ideas why JaxB does not work at this point?
You are passing all your parameters with #Form annotation.
But the Form part of the http message must be an xml data structure.
Your 3 parameters don't have a main xml datastructure so it wont work.
In short, form params are send as body.
Cxf use the MultivaluedMap to send params (cxf have an xml model for this structure).
As you can see it is not fit for parameters that can't be trivally serialized.
Here me solution would be to drop the #FormParam to avoid the problem:
1) Use #PathParam #CookieParam to send yours first 2 parameters, and the 'no tag' (body) only for the army compositions.
2) Define an uber object that take all parameters and can be serialized as xml datastructure and use the 'no tag' (body) sending.
3) Use soap, with cxf it is really easy to gets both Rest and Soap.
I have structure IntEx - in short it extends normal Int32 and processed operation. It looks like this:
[Serializable]
public struct IntEx
{
private int internalValue;
private IntEx(int value)
{
internalValue = value;
}
public static implicit operator int(IntEx value)
{
return value.internalValue;
}
public static implicit operator IntEx(int value)
{
return new IntEx(value);
}
}
If we send this structure through WCF it serialize using JSON and output will "nicely look". Like we will use sample code below:
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(TestClass));
using (MemoryStream stream = new MemoryStream())
{
jsonSerializer.WriteObject(stream, testClass);
string serializedString = Encoding.UTF8.GetString(stream.GetBuffer());
Console.WriteLine("JSON: {0}", serializedString);
}
public class TestClass
{
public int I1 { get; set; }
public IntEx I2 { get; set; }
}
Output look like this
JSON: {"I1":11,"I2":{"internalValue":22}}
Client and other "third-part" progam use this format (with internalValue).
Using IntEx is widely use in my application. One of the object is serialized to XML (some kind of setting). This object use IntEx as type.
So I have to implement IXmlSerializable to structure IntEx, because without this property is serialized like empty node
XML: <TestClass><I1>11</I1><I2 /></TestClass>
If I change IntEx to use IXmlSerializable
[Serializable]
public struct IntEx : IXmlSerializable
{
private int internalValue;
private IntEx(int value)
{
internalValue = value;
}
public static implicit operator int(IntEx value)
{
return value.internalValue;
}
public static implicit operator IntEx(int value)
{
return new IntEx(value);
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
throw new NotImplementedException();
}
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteValue(internalValue);
}
}
XML output look ok
XML: <TestClass><I1>11</I1><I2>22</I2></TestClass>
but all my service break down, since now JSON look like this:
JSON: {"I1":11,"I2":"<IntEx xmlns=\"http:\/\/schemas.datacontract.org\/2004\/07\/TestJSONSerialization\">22<\/IntEx>"}
I read that if you use IXmlSerializable, JSON serialization "think" that I'm responsible for serialize so leave this object to me... But how can I change back to "original" serialization.
So now I'm in deadend... I need JSON output look like before, but I also need to some how force to write setting to XML with two conditions:
internalValue should remain private - it shouldn't be accessible using some public Property
I don't want rewrite bunch of code to chaneg (use boxing for JSON's properties) or change all possible property or class that can be saved to XML file.
So can anyone give me some clue, how I can resolve this issue? :/
you can use DataContractJsonSerializer with IDataContractSurrogate. using the IDataContractSurrogate to convert "IntEx" to "IntExJson", and the "IntExJson" don't need to inherit from IXmlSerializable.
IDataContractSurrogate can be used to remove some features from object, and convert to the similar object. and then use:
public DataContractJsonSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, IDataContractSurrogate dataContractSurrogate, bool alwaysEmitTypeInformation);
to serialize the object to json. the deserialization is same.
I am using an abstract class to localize my models to different languages. This is the inheritance chain that I have set up:
//Base model, contains localized fields
public class Restaurant extends LocalizedModel<LocalizedRestaurantData>{
...
}
//Abstract class to support localized fields for all my models
#XmlRootElement
public abstract class LocalizedModel<T extends LocalizedData> {
private T en;
public T getEn() {
return en;
}
public void setEn(T en) {
this.en = en;
}
...
}
//Implementation of the localized fields for the restaurant class.
#XmlRootElement
public class LocalizedRestaurantData extends LocalizedData{
protected String name;
protected String address;
...
}
This all works fine in my Jersey JSON web service, except for one thing: All the instances of the localized property en contain an extra field type:
Restaurant JSON:
{
"en": {
"type": "localizedRestaurantData",
"address": "1234 Main St.",
"name": "Tacos Folie"
},
...
}
This type field is undesired and undesirable especially since it seems to be also required by Jackson when parsing an object. I've added #JsonIgnoreProperties({"type"}) in my code without success.
After multiple attempts and soliciting help on both the Jackson and the Jersey mailing list, the solution I found is:
My JERSEY context was implementing ContextResolver<JSONJAXBContext>. That needs to be changed to ContextResolver<JacksonJsonProvider> to use the pure JSON parser.
Secondly, the JacksonJsonProvider needs to configured as follows:
JacksonJsonProvider jjp = new JacksonJsonProvider();
jjp.configure(Feature.WRITE_NULL_MAP_VALUES, false);
jjp.configure(Feature.WRITE_NULL_PROPERTIES, false);
And used as the context.
Finally, the following method needs to be overriden as follows in the ContextResolver:
#Override
public Set<Object> getSingletons() {
Set<Object> s = new HashSet<Object>();
JacksonJsonProvider jjp = new JacksonJsonProvider();
jjp.configure(Feature.WRITE_NULL_MAP_VALUES, false);
jjp.configure(Feature.WRITE_NULL_PROPERTIES, false);
s.add(jjp);
return s;
}
I'm using Spring Roo which generated set of hibernate and FlexJSON classes.
I have entity called Location and entity called Comment.
Location has many comments (1:M).
I'm trying to generate JSON object, which will, when deserialized and inserted reference existing Location object.
When I omit location field, everything is working fine, for example:
{
"date": 1315918228639,
"comment": "Bosnia is very nice country"
}
I don't know how to reference location field.
I've tried following, but with little success:
{
"location": 10,
"date": 1315918228639,
"comment": "Bosnia is very nice country"
}
where location id is 10.
How can I reference location field in the JSON?
Edit: Added Comment entity:
#RooJavaBean
#RooToString
#RooJson
#RooEntity
public class Komentar {
private String comment;
#ManyToOne
private Location location;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(style = "M-")
private Date date;
}
I've solved issue by adding transient property.
#Transient
public long getLocationId(){
if(location!=null)
return location.getId();
else
return -1;
}
#Transient
public void setLocationId(long id){
location = Location.findLocation(id);
}
Got similar problem, but i can't change incoming json message, so i've changed generated aspect file:
#RequestMapping(value = "/jsonArray", method = RequestMethod.POST, headers = "Accept=application/json")
public ResponseEntity<String> Komentar.createFromJsonArray(#RequestBody String json) {
for (Komentar komentar: Komentar.fromJsonArrayToProducts(json)) {
komentar.setLocation(Location.findLocation(komentar.getLocation().getId()));
komentar.persist();
}
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
return new ResponseEntity<String>(headers, HttpStatus.CREATED);
}
komentar.setLocation(Location.findLocation(komentar.getLocation().getId())); was added by me.
I got same problem and solved it by introducing a custom object factory.
Since JSONDeserializer expect a json object for location attribute (ex:"Location":{"id":10,..}), supplying location id as a String/Integer (ex:"Location":"10") will give you an exception.
Therefore I have written LocationObjectFactory class and telling flexjson how to deserialize a Location class object in the way I want.
public class LocationObjectFactory implements ObjectFactory {
#Override
public Object instantiate(ObjectBinder context, Object value,
Type targetType, Class targetClass) {
if(value instanceof String){
return Location.findProblem(Long.parseLong((String)value));
}
if(value instanceof Integer){
return Location.findProblem(((Integer)value).longValue());
}
else {
throw context.cannotConvertValueToTargetType(value,targetClass);
}
}
}
and deserialize the json string like this
new JSONDeserializer<Komentar>().use(null, Komentar.class).use(Location.class, new LocationObjectFactory()).deserialize(json);