Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search query to convert either "foo":"bar" OR "foo":["bar","baz"] into an IList<String> ? #227

Closed
mvincent opened this issue Apr 14, 2013 · 5 comments

Comments

@mvincent
Copy link

When I execute this NEST query:

client.Search<Entity>(s => s
    .Index("index")
    .Type("entities")
    .Query(q1 => q1
        .Filtered(cs => cs
            .Query(q2 => q2.MatchAll())
            .Filter(f => f
                .Nested(n => n
                    .Path(p => p.Codes)
                    .Query(q3 => q3
                        .Bool(b => b
                            .Must(m => m
                                .Text(t => t
                                    .OnField(f2 => f2.Codes.Ancestry) 
                                    .QueryString(ancestry) 
                                ) 
                            )
                        )
                    )
                )
            )
        )
    )
);

where my Entity mapping is:

curl -XPUT "localhost:9200/index/entities/_mapping" -d '{
        "entities_mapping": {
        "properties" : {
            "codes" : {
                "type" : "nested",
                "properties" : {
                    "ancestry": {
                        "type": "string",
                        "index_analyzer": "depth_path_hierarchy"
                    }
                }
            }
        }
    }
}'

end my entity class is:

    public class Entity
    {
        public int Id { get; set; }
        public EntityCodes Codes { get; set; }
        ...
        ...
    }

    public class EntityCodes
    {
        public IList<String> Ancestry { get; set; }
        ...
        ...
    }

the response from elasticsearch varies depending on whether there is one or many ancestry values (e.g. whether the result is a simple name/value pair or an actual JSON array with square brackets). An example response is below:

{
      "_index" : "index",
      "_type" : "entities",
      "_id" : "1",
      "_score" : 1.0, "_source" : {
"codes":{
    "ancestry":[
        "/category1/subcategoryA",
        "/category1/subcategoryB"
    ]
 },
"Id":1}
}, 
{
      "_index" : "index",
      "_type" : "entities",
      "_id" : "2",
      "_score" : 1.0, "_source" : {
"codes":{
    "ancestry":
        "/category1/subcategoryA"
 },
"Id":2}
}

If the ancestry has more than one entry, the JSON to class conversion goes well (because it's represented as a proper JSON array with square brackets). However, if the ancestry is just a simple name/value pair (as in the result above with Id of 2), then I receive this error:

Newtonsoft.Json.JsonSerializationException: 
Error converting value "/category1/subcategoryA" 
to type 'System.Collections.Generic.IList`1[System.String]'.
Path '_source.codes.ancestry', line 1, position 6880. 
---> System.ArgumentException:
Could not cast or convert from 
System.String to System.Collections.Generic.IList`1[System.String].
Result StackTrace:  
at Newtonsoft.Json.Utilities.ConvertUtils.EnsureTypeAssignable
(Object value, Type initialType, Type targetType)
   at Newtonsoft.Json.Utilities.ConvertUtils.ConvertOrCast
(Object initialValue, CultureInfo culture, Type targetType)

Do you have any recommendations on the best way to achieve this conversion? Thank you.

@mvincent
Copy link
Author

The mapping I have is a little odd because it is representative of how an Elasticsearch JDBC river import data from the related database tables (an entity table with a zero-to-many relationship to a code table).

@Mpdreamz
Copy link
Member

Hey @mvincent

You can create a custom json converter for a type/field and give it to Nest by calling AddConverter() on the client, I will most likely move this call to ConnectionSettings in the 1.0 release though.

I hope this helps! If you manage to write a converter please post it back, might be worthwhile to add it to Nest ElasticProperty i.e

[ElasticProperty(AutoConvertValueTypeToList=true)]
public IList<string> MyProp { get; set; }

@mvincent
Copy link
Author

Thanks for pointing me in the right direction.

I referenced this approach for a converter ( http://bit.ly/nest227 ) in order to create the following JsonToListConverter:

public class JsonToListConverter<T> : CustomCreationConverter<IList<T>>
{
    public override IList<T> Create(Type objectType)
    {
        // Default value is an empty list.
        return new List<T>();
    }

    public override object ReadJson(JsonReader reader, Type objectType,
           object existingValue, JsonSerializer serializer)
    {

        if (reader.TokenType == JsonToken.StartArray)
        {
            // JSON object was already an array, so just deserialize it as usual.
            object result = serializer.Deserialize(reader, objectType);
            return result;
        }
        else
        {
            // JSON object was not an array, 
            // so deserialize the object and wrap it in a List.
            var resultObject = serializer.Deserialize<T>(reader);
            var correctedResult = new List<T>();
            correctedResult.Add(resultObject);
            return correctedResult;
        }
    }
}

Then I applied the JsonConverter attribute on the model property:

public class EntityCodes
{
    [JsonConverter(typeof(JsonToListConverter<String>))]
    public IList<String> Ancestry { get; set; }
}

@ninjaboy
Copy link

Thank you, gentlemen. This saved a bit of nerves to me.

@EricKeij
Copy link

EricKeij commented Jan 2, 2017

Is something like this added into a current NEST version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants