Home > Software design >  expected END_OBJECT but found FIELD_NAME
expected END_OBJECT but found FIELD_NAME

Time:06-09

I'm using PostAsJsonAsync in the HttpClient to query Elastic, and it's failing on line 12, "wildcard"

I used https://json2csharp.com/ to convert the example JSON to C# objects.

This is the json that's being produced by Newtonsoft which fails.

{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "@timestamp": {
              "gte": "now-7d",
              "lt": "now"
            }
          },
          "wildcard": {
            "request.keyword": {
              "value": "/message/*/*-message/2c35669dd87e471faad1f90374d8d380/status",
              "case_insensitive": true
            }
          }
        }
      ]
    }
  }
}

This is an example that I was provided and used to convert the json to C# objects.

{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "@timestamp": {
              "gte": "now-7d",
              "lt": "now"
            }
          }
        },
              {
          "wildcard": {
            "request.keyword": {
              "value": "/message/*/*-message/2c35669dd87e471faad1f90374d8d380/status",
              "case_insensitive": true
            }
          }
        }
      ]     
    }
  }
}

Both are valid JSON, but only the 2nd one is accepted by Elastic. It's seems to be expecting curly braces around the properties in must, but I can't figure out how to get the JSON to serialize this way.

CodePudding user response:

You are encountering a limitation with code-generation tools such as https://json2csharp.com/, namely that they do not handle implied polymorphism very well. In such cases you may need to manually fix the generated classes.

Consider the following JSON array containing two different types of object:

[{"A" : "a value"},{"B" : "b value"}]

The array contains objects that either have a property A or a property B, but if you generate classes from this JSON, you will get a single, merged type with both properties:

public class Root
{
    public string A { get; set; }
    public string B { get; set; }
}

Whereas what you really want is something like:

public interface IRootBase { }

public class A : IRootBase 
{
    public string A { get; set; }
}

public class B : IRootBase 
{
    public string B { get; set; }
}

Given such a model, you will be able to construct a List<IRootBase> and serialize it to get the JSON shown. (And, to deserialize, see Deserializing polymorphic json classes without type information using json.net.)

In your case, the problem is with the array value of "must". As you can see this array contains two different types of object:

[
   {
      "range":{
         "@timestamp":{
            "gte":"now-7d",
            "lt":"now"
         }
      }
   },
   {
      "wildcard":{
         "request.keyword":{
            "value":"/message/*/*-message/2c35669dd87e471faad1f90374d8d380/status",
            "case_insensitive":true
         }
      }
   }
]

But https://json2csharp.com/ will create the following combined type:

public class Must
{
    public Range range { get; set; }
    public Wildcard wildcard { get; set; }
}

If you were to create an array with a single instance of Must containing both properties, you would get the invalid JSON rejected by Elastic.

Instead, you need to manually modify the auto generated types as follows:

#region Manually created from Must

public interface IMustConstraint { }

public class RangeConstraint : IMustConstraint
{
    public Range range { get; set; }
}

public class WildcardConstraint : IMustConstraint
{
    public Wildcard wildcard { get; set; }
}

#endregion Manually created from Must

public class Range
{
    [JsonProperty("@timestamp")]
    public Timestamp Timestamp { get; set; }
}

public class Timestamp
{
    public string gte { get; set; }
    public string lt { get; set; }
}

public class Wildcard
{
    [JsonProperty("request.keyword")]
    public RequestKeyword RequestKeyword { get; set; }
}

public class RequestKeyword
{
    public string value { get; set; }
    public bool case_insensitive { get; set; }
}

public class BoolQuery // Renamed from Bool for clarity
{
    public List<IMustConstraint> must { get; set; } // Modified from List<Must>
}

public class Query
{
    public BoolQuery @bool { get; set; }
}

public class Root
{
    public Query query { get; set; }
}

And now you will be able to do:

Root root = new ()
{
    query = new ()
    {
        @bool = new ()
        {
            must = new ()
            {
                new RangeConstraint() { range = new () { Timestamp = new () { gte = "now-7d", lt = "now" } } },
                new WildcardConstraint() { wildcard = new () { RequestKeyword = new () { value = "/message/*/*-message/2c35669dd87e471faad1f90374d8d380/status", case_insensitive = true } } },
            },
        },
    },
};

var json = JsonConvert.SerializeObject(root, Formatting.Indented);

And create your required JSON.

Demo fiddle here.

  • Related