Using the Jackson Hash Mapper with Flatten=true my Date fields are getting discarded. Is this the correct behaviour or a bug? Is there a way to have Date serialized with Flatten=true?
I've used the following test Pojo:
import java.util.Date;
public class FooClass{
private Boolean foolean;
private Integer barteger;
private String simpleString;
private Date myDate;
public void setFoolean(Boolean value){ foolean = value; }
public Boolean getFoolean(){ return foolean; }
public void setBarteger(Integer value){ barteger = value; }
public Integer getBarteger(){ return barteger; }
public void setSimpleString(String value) { simpleString = value; }
public String getSimpleString(){ return simpleString; }
public void setMyDate(Date value) { myDate = value; }
public Date getMyDate(){ return myDate; }
}
public class Main {
public static void main(String[] args) throws ParseException,
JsonParseException, JsonMappingException, IOException {
Jackson2HashMapper hashMapper = new Jackson2HashMapper(true);
FooClass fooObject = new FooClass();
fooObject.setFoolean(true);
fooObject.setBarteger(10);
fooObject.setSimpleString("Foobar");
fooObject.setMyDate(new Date());
Map<String, Object> hash = hashMapper.toHash(fooObject);
for (String key: hash.keySet())
{
System.out.println("hash contains: " + key + "=" +
hash.get(key.toString()));
}
FooClass newFoo = (FooClass)(hashMapper.fromHash(hash));
System.out.println("FromHash: " + newFoo);
}
}
In this case I get the following output:
hash contains: #class=FooClass
hash contains: foolean=true
hash contains: barteger=10
hash contains: simpleString=Foobar
FromHash: FooClass#117159c0
If I change new Jackson2HashMapper(false); then I get:
hash contains: #class=FooClass
hash contains: foolean=true
hash contains: barteger=10
hash contains: simpleString=Foobar
hash contains: myDate=[java.util.Date, 1547033077869]
FromHash: FooClass#7ed7259e
I was expecting to get the Date field serialized in both cases - perhaps with an additional field describing the date type (flattened).
I traced the reason for this to the following line in the HashMapper code:
typingMapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
Where the mapper is configured.
It seems to issue in Jackson2HashMapper.
After digging into the source of Jackson2HashMapper, it seems to issue in Jackson2HashMapper.
created an issue for this, DATAREDIS-1001
Jackson2HashMapper does not serialize Date/Calender fields when flatten = true
Related
I am trying to read a json message from kafka and got an exception, which says Jackson cannot deserialize the json to POJO.
The json is like {"code":"500","count":22,"from":1528343820000,"to":1528343880000}, which is an output of kafka stream.
The POJO declares all attributes of the json, and is exactly the same POJO to produce the json message. So I have no idea why it would happen.
I am using spring cloud stream 2.0.0.RELEASE.
Any help would be appreciated. Thanks.
POJO:
public class CodeCount {
private String code;
private long count;
private Date from;
private Date to;
#Override
public String toString() {
return "CodeCount [code=" + code + ", count=" + count + ", from=" + from + ", to=" + to + "]";
}
public CodeCount(String code, long count, Date from, Date to) {
super();
this.code = code;
this.count = count;
this.from = from;
this.to = to;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
public Date getFrom() {
return from;
}
public void setFrom(Date from) {
this.from = from;
}
public Date getTo() {
return to;
}
public void setTo(Date to) {
this.to = to;
}
}
Stacktrace:
2018-06-07 15:18:51.572 ERROR 1 --- [container-0-C-1] o.s.integration.handler.LoggingHandler : org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Cannot construct instance of `com.example.CodeCount` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"{"code":"500","count":22,"from":1528343820000,"to":1528343880000}"; line: 1, column: 2]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.CodeCount` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67) ~[jackson-databind-2.9.3.jar!/:2.9.3]
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451) ~[jackson-databind-2.9.3.jar!/:2.9.3]
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027) ~[jackson-databind-2.9.3.jar!/:2.9.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1275) ~[jackson-databind-2.9.3.jar!/:2.9.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:325) ~[jackson-databind-2.9.3.jar!/:2.9.3]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159) ~[jackson-databind-2.9.3.jar!/:2.9.3]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001) ~[jackson-databind-2.9.3.jar!/:2.9.3]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3109) ~[jackson-databind-2.9.3.jar!/:2.9.3]
at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertFromInternal(MappingJackson2MessageConverter.java:221) ~[spring-messaging-5.0.6.RELEASE.jar!/:5.0.6.RELEASE]
... 37 common frames omitted
Jackson needs access to the default constructor to deserialize, add the default constructor to the pojo, ie:
public CodeCount() {
}
You can annotate the existing constructor, and args, and Jackson will use this:
#JsonCreator
public CodeCount(#JsonProperty("code") String code,
#JsonProperty("count") long count,
#JsonProperty("from") Date from,
#JsonProperty("to") Date to) {
super();
this.code = code;
this.count = count;
this.from = from;
this.to = to;
}
Passing in the dates may complicate it a bit, but it is definitely still possible.
Document structure is:
db.lookupdata.insert({ parent_key : "category" , key : "accessories" , value : ["belts","cases","gloves","hair","hats","scarves","sunglasses","ties","wallets","watches"]})
i want to store array filed values in java array list
i am finding the document like this:
FindIterable<Document> iterable1 = docCollectionLookup.find(Filters.eq("parent_key", "category"));
Iterator<Document> iter1=iterable1.iterator();
while(iter1.hasNext())
{
Document theObj = iter1.next();
categotyLookUpMap.put(theObj.getString("key"), list);
}
now here how can i retrieve array field values(key:"value") in ArrayList
You can retrieve array field values(key:"value") in ArrayList just like how you retrieve string field key. Please refer below:
FindIterable<Document> iterable1 = docCollectionLookup.find(Filters.eq("parent_key", "category"));
Iterator<Document> iter1=iterable1.iterator();
//Create a HashMap variable with type <String,ArrayList>,according to your needs
Map<String,ArrayList> categotyLookUpMap = new HashMap<String,ArrayList>();
while(iter1.hasNext())
{
Document theObj = iter1.next();
//Get method of Document class will return object,parse it to ArrayList
categotyLookUpMap.put(theObj.getString("key"), (ArrayList)theObj.get("value"));
}
Alternatively, you can use Morphia which is MongoDB object-document mapper in Java. You can setup dependency / download JAR from here
First, create LookupData class to map to lookupdata collection. Annotation #Id is required else will throw exception with message "No field is annotated with #Id; but it is required". So create an _id field for it.
#Entity("lookupdata")
public class LookupData {
#Id
String _id ;
#Property("parent_key")
String parentKey;
String key;
ArrayList<String> value;
public String get_id() {
return _id;
}
public void set_id(String _id) {
this._id = _id;
}
public String getParentKey() {
return parentKey;
}
public void setParentKey(String parentKey) {
this.parentKey = parentKey;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public void setValue(ArrayList<String> value) {
this.value = value;
}
public ArrayList<String> getValue() {
return value;
}
}
Retrieve array field values as below:
MongoClient mongoClient = new MongoClient(new MongoClientURI("mongodb://localhost"));
Morphia morphia = new Morphia();
morphia.map(LookupData.class);
//lookupdata collection is under my local db "tutorials" in this case
Datastore datastore = morphia.createDatastore(mongoClient, "tutorials");
Map<String,ArrayList> categotyLookUpMap = new HashMap<String,ArrayList>();
LookupData lookupData = datastore.find(LookupData.class).get();
categotyLookUpMap.put(lookupData.getKey(), lookupData.getValue());
i have two Presenters: A DevicePresenter and a ContainerPresenter. I place a PlaceRequest in the DevicePresenter to call the ContainerPresenter with some parameters like this:
PlaceRequest request = new PlaceRequest.Builder()
.nameToken("containersPage")
.with("action","editContainer")
.with("containerEditId", selectedContainerDto.getUuid().toString())
.build();
placeManager.revealPlace(request);
In my ContainersPresenter i have this overridden method:
#Override
public void prepareFromRequest(PlaceRequest placeRequest) {
Log.debug("prepareFromRequest in ContainersPresenter");
super.prepareFromRequest(placeRequest);
String actionString = placeRequest.getParameter("action", "");
String id;
//TODO: Should we change that to really retrieve the object from the server? Or should we introduce a model that keeps all values and inject that into all presenters?
if (actionString.equals("editContainer")) {
try {
id = placeRequest.getParameter("id", null);
for(ContainerDto cont : containerList) {
Log.debug("Compare " + id + " with " + cont.getUuid());
if(id.equals(cont.getUuid())) {
containerDialog.setCurrentContainerDTO(new ContainerDto());
addToPopupSlot(containerDialog);
break;
}
}
} catch (NumberFormatException e) {
Log.debug("id cannot be retrieved from URL");
}
}
}
But when revealPlace is called, the URL in the browser stays the same and the default presenter (Home) is shown instead.
When i print the request, it seems to be fine:
PlaceRequest(nameToken=containersPage, params={action=editContainer, containerEditId=8fa5f730-fe0f-11e3-a3ac-0800200c9a66})
And my NameTokens are like this:
public class NameTokens {
public static final String homePage = "!homePage";
public static final String containersPage = "!containersPage";
public static final String devicesPage = "!devicesPage";
public static String getHomePage() {
return homePage;
}
public static String getDevicesPage() {
return devicesPage;
}
public static String getContainersPage() {
return containersPage;
}
}
What did i miss? Thanks!
In your original code, when constructing your PlaceRequest, you forgot the '!' at the beginning of your nametoken.
.nameToken("containersPage")
while your NameTokens entry is
public static final String containersPage = "!containersPage";
As you noted, referencing the constant in NameTokens is less prone to such easy mistakes to make!
Sometimes the problem exists "between the ears". If i avoid strings but use the proper symbol from NameTokens like
PlaceRequest request = new PlaceRequest.Builder()
.nameToken(NameTokens.containersPage)
.with("action","editContainer")
.with("containerEditId", selectedContainerDto.getUuid().toString())
.build();
it works just fine. Sorry!
Can you tell me how can I use Gson to extract two distinct object defined in this string:
http://json.parser.online.fr/
I attempt to use
gson.fromJson(json, SeedAttribs.class);
and
gson.fromJson(json, SettingsAttribs.class);
but neither is working. I'm surprised why not.
I would also need a way to replace this json string when particular object eg. SeedAttribs changes. I need a way to rewrite it while SettingsAttribs will NOT change at all.
How can this be done?
Thank you for answers!
Make sure your original JSon string contains correct format. The best way is to serialize your object with GSON. Here is the code:
public static void main(String[] args)
{
Gson gson = new Gson();
SettingsAttribs st = new SettingsAttribs();
st.setField1("value1");
st.setField2("value2");
// report constructed object
System.out.println("st: " + st);
// serialize to json
String json = gson.toJson(st, SettingsAttribs.class);
System.out.println("json: " + json);
// deserialize form json
SettingsAttribs restoredSettings = gson.fromJson(json, SettingsAttribs.class);
System.out.println("restoredSettings: " + restoredSettings);
}
It compiles and runs. The output it produces:
st: SettingsAttribs [field1=value1, field2=value2]
json: {"field1":"value1","field2":"value2"}
restoredSettings: SettingsAttribs [field1=value1, field2=value2]
And SettingsAttribs class:
public class SettingsAttribs
{
private String field1;
private String field2;
public String getField1()
{
return field1;
}
public void setField1(String field1)
{
this.field1 = field1;
}
public String getField2()
{
return field2;
}
public void setField2(String field2)
{
this.field2 = field2;
}
#Override
public String toString()
{
return "SettingsAttribs [field1=" + field1 + ", field2=" + field2 + "]";
}
}
We are using fluentnhibernate with automapping and we have a naming convention that all columns that are foreign keys, there column name will end with "Key". So we have a convention that looks like this:
public class ForeignKeyColumnNameConvention : IReferenceConvention
{
public void Apply ( IManyToOneInstance instance )
{
// name the key field
string propertyName = instance.Property.Name;
instance.Column ( propertyName + "Key" );
}
}
This works great until we created a component in which one of its values is a foreign key. By renaming the column here it overrides the default name given to the component column which includes the ComponentPrefix which is defined in the AutomappingConfiguration. Is there a way for me to get the ComponentPrefix in this convention? or is there some other way for me to get the column name for components with a property that is a foreign key to end in the word "Key"?
After a lot of fiddling and trial & error (thus being tempted to use your solution with Reflection) I came up with the following:
This method depends on the order of the execution of the conventions. This convention-order happens via a strict hierarchy. In this example, at first, the convention of the component (IDynamicComponentConvention) is being handled and after that the conventions of the inner properties are being handled such as the References mapping (IReferenceConvention).
The strict order is where we make our strike:
We assemble the correct name of the column in the call to Apply(IDynamicComponentConvention instance), put it on the queue. Note that a Queue<T> is used which is a FIFO (first-in-first-out) collection type thus it keeps the order correctly.
Almost immediately after that, Apply(IManyToOneInstanceinstance) is called. We check if there is anything in the queue. If there is, we take it out of the queue and set it as column name. Note that you should not use Peek() instead of Dequeue() as it does not remove the object from the queue.
The code is as follows:
public sealed class CustomNamingConvention : IDynamicComponentConvention, IReferenceConvention {
private static Queue<string> ColumnNames = new Queue<string>();
public void Apply(IDynamicComponentInstance instance) {
foreach (var referenceInspector in instance.References) {
// All the information we need is right here
// But only to inspect, no editing yet :(
// Don't worry, just assemble the name and enqueue it
var name = string.Format("{0}_{1}",
instance.Name,
referenceInspector.Columns.Single().Name);
ColumnNames.Enqueue(name);
}
}
public void Apply(IManyToOneInstance instance) {
if (!ColumnNames.Any())
// Nothing in the queue? Just return then (^_^)
return;
// Set the retrieved string as the column name
var columnName = ColumnNames.Dequeue();
instance.Column(columnName);
// Pick a beer and celebrate the correct naming!
}
}
I Have figured out a way to do this using reflection to get to the underlying mapping of the IManyToOneInspector exposed by the IComponentInstance but was hoping there was a better way to do this?
Here is some example code of how I achieved this:
#region IConvention<IComponentInspector, IComponentInstance> Members
public void Apply(IComponentInstance instance)
{
foreach (var manyToOneInspector in instance.References)
{
var referenceName = string.Format("{0}_{1}_{2}{3}", instance.EntityType.Name, manyToOneInspector.Property.PropertyType.Name, _autoMappingConfiguration.GetComponentColumnPrefix(instance.Property), manyToOneInspector.Property.Name);
if(manyToOneInspector.Property.PropertyType.IsSubclassOf(typeof(LookupBase)))
{
referenceName += "Lkp";
}
manyToOneInspector.Index ( string.Format ( "{0}_FK_IDX", referenceName ) );
}
}
#endregion
public static class ManyToOneInspectorExtensions
{
public static ManyToOneMapping GetMapping(this IManyToOneInspector manyToOneInspector)
{
var fieldInfo = manyToOneInspector.GetType ().GetField( "mapping", BindingFlags.NonPublic | BindingFlags.Instance );
if (fieldInfo != null)
{
var manyToOneMapping = fieldInfo.GetValue( manyToOneInspector ) as ManyToOneMapping;
return manyToOneMapping;
}
return null;
}
public static void Index(this IManyToOneInspector manyToOneInspector, string indexName)
{
var mapping = manyToOneInspector.GetMapping ();
mapping.Index ( indexName );
}
public static void Column(this IManyToOneInspector manyToOneInspector, string columnName)
{
var mapping = manyToOneInspector.GetMapping ();
mapping.Column ( columnName );
}
public static void ForeignKey(this IManyToOneInspector manyToOneInspector, string foreignKeyName)
{
var mapping = manyToOneInspector.GetMapping();
mapping.ForeignKey ( foreignKeyName );
}
}
public static class ManyToOneMappingExtensions
{
public static void Index (this ManyToOneMapping manyToOneMapping, string indexName)
{
if (manyToOneMapping.Columns.First().IsSpecified("Index"))
return;
foreach (var column in manyToOneMapping.Columns)
{
column.Index = indexName;
}
}
public static void Column(this ManyToOneMapping manyToOneMapping, string columnName)
{
if (manyToOneMapping.Columns.UserDefined.Count() > 0)
return;
var originalColumn = manyToOneMapping.Columns.FirstOrDefault();
var column = originalColumn == null ? new ColumnMapping() : originalColumn.Clone();
column.Name = columnName;
manyToOneMapping.ClearColumns();
manyToOneMapping.AddColumn(column);
}
public static void ForeignKey(this ManyToOneMapping manyToOneMapping, string foreignKeyName)
{
if (!manyToOneMapping.IsSpecified("ForeignKey"))
manyToOneMapping.ForeignKey = foreignKeyName;
}
}