I'm quite new to spring expression language. I have a map which contains values as string.
{
"id": "1",
"object": {
"object1": {
"fields": {
"value1": {
"value":"3"
},
"value2": {
"value":"2"
}
}
}
}
}
The spring expression that i have looks something like this,
((object['object1'].fields['value1'].value * object['object1'].fields['value2'].value) * 0.5)
My calculation method looks like this where SampleObject class has the map object
public Double calculation(String expression, SampleObject sampleObject){
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
Double evaluatedValue = (Double)exp.getValue(sampleObject);
return evaluatedValue;
}
Since the value is string, I get an error saying cannot convert String to Double. This can be eliminated if i change the expression like this
((new Double(object['object1'].fields['value1'].value) * new Double(object['object1'].fields['value2'].value)) * 0.5)
But I'm looking for a solution where I don't have to change the expression or the object. Is there a solution.
Thanks in advance
You can plugin a class that supports operations on those operand types:
class StringDoubleOperatorOverloader implements OperatorOverloader {
#Override
public boolean overridesOperation(Operation operation, Object leftOperand,
Object rightOperand) throws EvaluationException {
return operation==Operation.MULTIPLY &&
leftOperand instanceof String &&
rightOperand instanceof String;
}
#Override
public Object operate(Operation operation, Object leftOperand, Object rightOperand)
throws EvaluationException {
Double l = Double.valueOf((String)leftOperand);
Double r = Double.valueOf((String)rightOperand);
return l*r;
}
}
Then pass a customized StandardEvaluationContext to getValue():
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext sec = new StandardEvaluationContext();
sec.setOperatorOverloader(new StringDoubleOperatorOverloader());
Double evaluatedValue = (Double)exp.getValue(sec,new SampleObject());
Since your JSON has value1.value and value2.value as String the Expression throws the Exception.
Either change your JSON to have Decimal values
{
"id": "1",
"object": {
"object1": {
"fields": {
"value1": {
"value":3.0
},
"value2": {
"value":2.0
}
}
}
}
}
Or in the Expression method instead of casting do parsing.
public Double calculation(String expression, SampleObject sampleObject){
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
Double evaluatedValue = Double.parseDouble(exp.getValue(sampleObject).toString()); // This line
return evaluatedValue;
}
Related
I am trying to implement the Hive UDF with Parameter and so I am extending GenericUDF class.
The problem is my UDF works find on String Datatype however it throws error if I run on other data types. I want UDF to run regardless of data type.
Would someone please let me know what's wrong with following code.
#Description(name = "Encrypt", value = "Encrypt the Given Column", extended = "SELECT Encrypt('Hello World!', 'Key');")
public class Encrypt extends GenericUDF {
StringObjectInspector key;
StringObjectInspector col;
#Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
if (arguments.length != 2) {
throw new UDFArgumentLengthException("Encrypt only takes 2 arguments: T, String");
}
ObjectInspector keyObject = arguments[1];
ObjectInspector colObject = arguments[0];
if (!(keyObject instanceof StringObjectInspector)) {
throw new UDFArgumentException("Error: Key Type is Not String");
}
this.key = (StringObjectInspector) keyObject;
this.col = (StringObjectInspector) colObject;
return PrimitiveObjectInspectorFactory.javaStringObjectInspector;
}
#Override
public Object evaluate(DeferredObject[] deferredObjects) throws HiveException {
String keyString = key.getPrimitiveJavaObject(deferredObjects[1].get());
String colString = col.getPrimitiveJavaObject(deferredObjects[0].get());
return AES.encrypt(colString, keyString);
}
#Override
public String getDisplayString(String[] strings) {
return null;
}
}
Error
java.lang.ClassCastException: org.apache.hadoop.hive.serde2.objectinspector.primitive.JavaIntObjectInspector cannot be cast to org.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector
I would suggest you to replace StringObjectInspector col with PrimitiveObjectInspector col and the corresponding cast this.col = (PrimitiveObjectInspector) colObject. Then there are two ways:
First is to process every possible Primitive type, like this
switch (((PrimitiveTypeInfo) colObject.getTypeInfo()).getPrimitiveCategory()) {
case BYTE:
case SHORT:
case INT:
case LONG:
case TIMESTAMP:
cast_long_type;
case FLOAT:
case DOUBLE:
cast_double_type;
case STRING:
everyting_is_fine;
case DECIMAL:
case BOOLEAN:
throw new UDFArgumentTypeException(0, "Unsupported yet");
default:
throw new UDFArgumentTypeException(0,
"Unsupported type");
}
}
Another way, is to use PrimitiveObjectInspectorUtils.getString method:
Object colObject = col.getPrimitiveJavaObject(deferredObjects[0].get());
String colString = PrimitiveObjectInspectorUtils.getString(colObject, key);
It just pseudocode like examples. Hope it helps.
I'm trying to work with the reddit JSON API. There are post data objects that contain a field called edited which may contain a boolean false if the post hasn't been edited, or a timestamp int if the post was edited.
Sometimes a boolean:
{
"edited": false,
"title": "Title 1"
}
Sometimes an int:
{
"edited": 1234567890,
"title": "Title 2"
}
When trying to parse the JSON where the POJO has the field set to int, I get an error: JsonDataException: Expected an int but was BOOLEAN...
How can I deal with this using Moshi?
I also ran into a similar problem where I had fields that were sometimes booleans, and sometimes ints. I wanted them to always be ints. Here's how I solved it with Moshi and kotlin:
Make a new annotation that you will use on fields to should convert from boolean to int
#JsonQualifier
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION)
annotation class ForceToInt
internal class ForceToIntJsonAdapter {
#ToJson
fun toJson(#ForceToInt i: Int): Int {
return i
}
#FromJson
#ForceToInt
fun fromJson(reader: JsonReader): Int {
return when (reader.peek()) {
JsonReader.Token.NUMBER -> reader.nextInt()
JsonReader.Token.BOOLEAN -> if (reader.nextBoolean()) 1 else 0
else -> {
reader.skipValue() // or throw
0
}
}
}
}
Use this annotation on the fields that you want to force to int:
#JsonClass(generateAdapter = true)
data class Discovery(
#Json(name = "id") val id: String = -1,
#ForceToInt #Json(name = "thanked") val thanked: Int = 0
)
The easy way might be to make your Java edited field an Object type.
The better way for performance, error catching, and appliaction usage is to use a custom JsonAdapter.
Example (edit as needed):
public final class Foo {
public final boolean edited;
public final int editedNumber;
public final String title;
public static final Object JSON_ADAPTER = new Object() {
final JsonReader.Options options = JsonReader.Options.of("edited", "title");
#FromJson Foo fromJson(JsonReader reader) throws IOException {
reader.beginObject();
boolean edited = true;
int editedNumber = -1;
String title = "";
while (reader.hasNext()) {
switch (reader.selectName(options)) {
case 0:
if (reader.peek() == JsonReader.Token.BOOLEAN) {
edited = reader.nextBoolean();
} else {
editedNumber = reader.nextInt();
}
break;
case 1:
title = reader.nextString();
break;
case -1:
reader.nextName();
reader.skipValue();
default:
throw new AssertionError();
}
}
reader.endObject();
return new Foo(edited, editedNumber, title);
}
#ToJson void toJson(JsonWriter writer, Foo value) throws IOException {
writer.beginObject();
writer.name("edited");
if (value.edited) {
writer.value(value.editedNumber);
} else {
writer.value(false);
}
writer.name("title");
writer.value(value.title);
writer.endObject();
}
};
Foo(boolean edited, int editedNumber, String title) {
this.edited = edited;
this.editedNumber = editedNumber;
this.title = title;
}
}
Don't forget to register the adapter on your Moshi instance.
Moshi moshi = new Moshi.Builder().add(Foo.JSON_ADAPTER).build();
JsonAdapter<Foo> fooAdapter = moshi.adapter(Foo.class);
We are using Swashbuckle to document our WebAPI project (using Owin) and are trying to modify the generated Swagger file of Swashbuckle.
With the DescribeAllEnumsAsStrings() and an enum property like below, we get an expected result:
class MyResponseClass {
public Color color;
}
enum Color {
LightBlue,
LightRed,
DarkBlue,
DarkRed
}
Swagger generated result:
"color": {
"enum": [
"LightBlue",
"LightRed",
"DarkBlue",
"DarkRed"
],
"type": "string"
},
The challenge for us is that we have some properties that are of type string but we are actually treating them as enum types. For example:
class MyResponseClass {
public string color;
}
The only possible values for this property are dark-blue, dark-red, light-blue, light-red.
So, we want something like below as the result:
"color": {
"enum": [
"light-blue",
"light-red",
"dark-blue",
"dark-red"
],
"type": "string"
},
We have lots of these properties with different values in different classes. It would be great to have a custom attribute like below to make it generic. I can't figure out how to create such an attribute and use it in Swashbuckle DocumentFilters or OperationFilters:
public MyEndpointResponseClass {
[StringEnum("booked", "confirmed", "reserved")]
public string status;
// Other properties
}
public MyEndpointRequestClass {
[StringEnum("dark-blue", "dark-red", "light-blue", "light-red")]
public string color;
// Other properties
}
Instead of a custom attribute (StringEnum) use an attribute that swagger already knows about, a little know attribute (I've never used it before):
[RegularExpression("^(dark-blue|dark-red|light-blue|light-red)")]
That will inject into the parameter.pattern and then we can read it from the IDocumentSchema and transform it into an enum, here is my code:
private class StringEnumDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry s, IApiExplorer a)
{
if (swaggerDoc.paths != null)
{
foreach (var path in swaggerDoc.paths)
{
ProcessOperation(path.Value.get);
ProcessOperation(path.Value.put);
ProcessOperation(path.Value.post);
ProcessOperation(path.Value.delete);
ProcessOperation(path.Value.options);
ProcessOperation(path.Value.head);
ProcessOperation(path.Value.patch);
}
}
}
private void ProcessOperation(Operation op)
{
if (op != null)
{
foreach (var param in op.parameters)
{
if (param.pattern != null)
{
param.#enum = param.pattern
.Replace("^", "")
.Replace("(", "")
.Replace(")", "")
.Split('|');
}
}
}
}
}
Here is a working example:
http://swashbuckletest.azurewebsites.net/swagger/ui/index?filter=TestStringEnum#/TestStringEnum/TestStringEnum_Post
And the code behind that is on GitHub:
TestStringEnumController.cs
SwaggerConfig.cs#L389
I have the following dictionary:
var dict = new Dictionary<string, object> {
{ "decimal", 3.503m },
{ "int", 45 }
};
var serializedString = dict.ToJson();
By default that is serialized as:
{ "decimal" : { "_t" : "System.Decimal", "_v" : "3.503" }, "int" : 45 }
If I override DecimalSerializer as:
BsonSerializer.RegisterSerializer<decimal>(new DecimalSerializer().WithRepresentation(BsonType.Double));
That only influences on how "_v" value is serialized, e.g.:
{ "decimal" : { "_t" : "System.Decimal", "_v" : 3.503 }, "int" : 45 }
Expected result:
{ "decimal" : 3.503, "int" : 45 }
Please advise
The cause of the .Net types in the bson, is the lack of type in the dictionary. The Bson serializers are trying to get enough state to restore the original object of the items in the dictionary. From the context (the dictionary) they are of type "object", so the .Net type is inserted to know enough when deserializing.
The following solutions answer your question but lose the type information for deserializing.
Solution 1: Change the dictionary type to <string, decimal>
var dict = new Dictionary<string, decimal> {
{ "decimal", 3.503m },
{ "int", 45 }
};
var serializedString = dict.ToJson();
Results in: { "decimal" : "3.503", "int" : "45" }
With your override of the decimal serializer, you get the expected result.
{ "decimal" : 3.503, "int" : 45 }
Solution 2: Change the dictionary type to <string, double>
var dict = new Dictionary<string, double> {
{ "decimal", (double)3.503m },
{ "int", 45 }
};
var serializedString = dict.ToJson();
Results in the expected result: { "decimal" : 3.503, "int" : 45 }
Solution 3: Use custom serializer
public class MyDictionarySerializer : SerializerBase<Dictionary<string, object>>
{
public override void Serialize(MongoDB.Bson.Serialization.BsonSerializationContext context, MongoDB.Bson.Serialization.BsonSerializationArgs args, Dictionary<string, object> dictionary)
{
context.Writer.WriteStartArray();
foreach (var item in dictionary)
{
context.Writer.WriteStartDocument();
context.Writer.WriteString(item.Key);
// TODO your converstions from object to double
var value = (double)item.Value;
context.Writer.WriteDouble(value);
context.Writer.WriteEndDocument();
}
context.Writer.WriteEndArray();
}
public override Dictionary<string, object> Deserialize(MongoDB.Bson.Serialization.BsonDeserializationContext context, MongoDB.Bson.Serialization.BsonDeserializationArgs args)
{
context.Reader.ReadStartArray();
var result = new Dictionary<string, object>();
while (true)
{
try
{
//this catch block only need to identify the end of the Array
context.Reader.ReadStartDocument();
}
catch (Exception exp)
{
context.Reader.ReadEndArray();
break;
}
var key = context.Reader.ReadString();
double value = context.Reader.ReadDouble();
result.Add(key, value);
context.Reader.ReadEndDocument();
}
return result;
}
}
As another option, it's possible to override object serializer
public class DecimalsOverridingObjectSerializer : ObjectSerializer
{
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) {
if (value != null && value is decimal) {
base.Serialize(context, args, Convert.ToDouble(value));
} else {
base.Serialize(context, args, value);
}
}
}
BsonSerializer.RegisterSerializer(typeof(object), new DecimalsOverridingObjectSerializer());
that still will not work for Hashtables.
Possible workaround for Hashtables:
public class DecimalsOverridingDictionarySerializer<TDictionary>:
DictionaryInterfaceImplementerSerializer<TDictionary>
where TDictionary : class, IDictionary, new()
{
public DecimalsOverridingDictionarySerializer(DictionaryRepresentation dictionaryRepresentation)
: base(dictionaryRepresentation, new DecimalsOverridingObjectSerializer(), new DecimalsOverridingObjectSerializer())
{ }
}
BsonSerializer.RegisterSerializer(typeof(Hashtable), new DecimalsOverridingDictionarySerializer<Hashtable>(DictionaryRepresentation.Document));
Hello MVC and LINQ Experts,
I have a Model that looks like this:
public class SomeClass : IValidatableObject
{
public string SomeString { get; set; }
public string SomeString2 { get; set; }
public int SomeInteger { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//... IF there is some error...THEN
yield return new ValidationResult("Some Error Message.", GetFieldNames(() => new []{ this.SomeString }));
}
}
As you can see, I am calling GetFieldNames that takes an expression, and returns to you the expression members as a string array. According to a book I read recently, the way to link an error to a field is to pass it as a string as follows:
yield return new ValidationResult("Some Error Message.", new []{ "SomeString" }));
But I wanted to be Strongly Typed, so here is the method that I wrote:
public static string[] GetFieldNames(Expression<Func<object[]>> exp)
{
//Build a string that will in the end look like this: field1,field2,field3
//Then we split(',') it into an array and return that string array.
string fieldnames = "";
MemberExpression body = exp.Body as MemberExpression;
if (body == null)
{
NewArrayExpression ubody = (NewArrayExpression)exp.Body;
foreach(MemberExpression exp2 in ubody.Expressions)
{
fieldnames += exp2.Member.Name + ",";
}
fieldnames = fieldnames.TrimEnd(',');
}
if(fieldnames.Length > 0)
return fieldnames.Split(',');
else
return new string[]{};
}
Current Usage:
GetFieldNames(() => new[] { this.SomeString , this.SomeString2 });
Output:
{ "SomeString" , "SomeString2" }
This works fine.
The problem is that if I use it as follows, it gives me an error (compile time):
GetFieldNames(() => new[] { this.SomeString , this.SomeInteger });
Error:
No best type found for implicitly-typed array
My Desired Output:
{ "SomeString" , "SomeInteger" }
I can't pass in an array of object because int is not a complex type.
How can I pass the function an expression array with int and string?
You could try passing an array of objects (which is what your expression expects) instead of trying to use an array initializer syntax:
GetFieldNames(() => new object[] { this.SomeString, this.SomeInteger });
This allows you to pass arbitrary object types.
You could define an interface IFieldName that enables usage in your list, and then implement it in different classes (int, error, string, etc.) for the actual types that occur in your processing.
This is roughly equivalent to defining an rray of object, but restores type-safety.
With the help of Darin Dimitri (the idea to pass a new object[] instead of new []
The following code will make sure that your IValidatableObject can now be strongly typed instead of just an array of strings.
public static string[] GetFieldNames(Expression<Func<object[]>> exp)
{
string fieldnames = "";
MemberExpression body = exp.Body as MemberExpression;
if (body == null)
{
NewArrayExpression ubody = (NewArrayExpression)exp.Body;
foreach (Expression exp2 in ubody.Expressions)
{
if (exp2 is MemberExpression) {
fieldnames += ((MemberExpression)exp2).Member.Name + ",";
}
else {
var op = ((UnaryExpression)exp2).Operand;
fieldnames += ((MemberExpression)op).Member.Name + ",";
}
}
fieldnames = fieldnames.TrimEnd(',');
}
if(fieldnames.Length > 0)
return fieldnames.Split(',');
else
return new string[]{};
}
Usage:
GetFieldNames(() => new object[] { this.SomeString, this.SomeInteger }));
Usage for MVC Validation:
yield return new ValidationResult("Some Error.", GetFieldNames(() => new object[] { this.SomeString, this.SomeInteger }));