0

I am using Newtonsoft.Json 11.0.2 in .Net core 2.0.

If i use JObject, i am able to SelectToken like so:

JObject.Parse("{\"context\":{\"id\":42}}").SelectToken("context.id")

Returns

42

However, if i use JRaw, i get null for the same path?

new JRaw("{\"context\":{\"id\":42}}").SelectToken("context.id")

returns

null

Due to how my code is setup, my model is already in JRaw, and converting it to JObject just to select this token seems like a waste of RAM (this call is on the hot path).

UPDATE Ok, my actual data comes down in a model where only one of the properties is JRaw, so i need something like the below to work:

 JsonConvert.DeserializeObject<Dictionary<string, JRaw>>(
 "{\"a\":{\"context\":{\"id\":42}}}")["a"].SelectToken("context.id")

The above returns null again.

zaitsman
  • 8,984
  • 6
  • 47
  • 79
  • 1
    try this => `JRaw.Parse("{\"context\":{\"id\":42}}").SelectToken("context.id")` is same as => `JToken.Parse("{\"context\":{\"id\":42}}").SelectToken("context.id");` – er-sho Oct 20 '18 at 12:07
  • you can use both of above to get specific token from your `raw` json – er-sho Oct 20 '18 at 12:09
  • @ershoaib very nice! Unfortunately, this highlighted that i oversimplified the examples provided, please see the update – zaitsman Oct 20 '18 at 12:24
  • I don't think it's possible to use `.SelectToken` with a `JRaw` instance. – Rui Jarimba Oct 20 '18 at 12:32
  • @RuiJarimba but the method is on the class: https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Linq_JRaw.htm? – zaitsman Oct 20 '18 at 12:42
  • `JRaw raw = ....; JToken.Parse(raw.Value.ToString()).SelectToken("context.id").Value()` – Rui Jarimba Oct 20 '18 at 12:43
  • @RuiJarimba Yes i can do that, but that creates an extra allocation in memory, and if JSON is huge, this is double RAM – zaitsman Oct 20 '18 at 12:43
  • @zaitsman yes the method is there, but it probably doesn't make sense for `JRaw` object. If you add a watch to a variable of type `JRaw` you'll check that many properties are `null`, `HasValue` is `false` and properties such as `First` and `Last` throw an `InvalidOperationException` – Rui Jarimba Oct 20 '18 at 12:46
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/182188/discussion-between-rui-jarimba-and-zaitsman). – Rui Jarimba Oct 20 '18 at 12:47
  • Possible duplicate of [JConstructor and JRaw in Json.NET](https://stackoverflow.com/questions/36958680/jconstructor-and-jraw-in-json-net) – shA.t Oct 20 '18 at 14:01
  • @shA.t I don't think this question is a duplicate of that one – Rui Jarimba Oct 20 '18 at 14:04
  • If you are deserializing JSON (rather than constructing from scratch using already-serialized JSON objects) then `JRaw` may not give you improved performance. When deserializing, [`JsonSerializerInternalReader`](https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs#L219) calls [`JRaw.Create()`](https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Linq/JRaw.cs#L59) which parses the JSON and copies it to a `JsonTextWriter`. Writing to a `JTokenWriter` may be just as fast, with faster postprocessing. – dbc Oct 20 '18 at 17:13

1 Answers1

2

Title might be a bit misleading, but basically what the OP needs is a way to parse an existing (and large) JRaw object without consuming too much memory.

I ran some tests and I was able to find a solution using a JsonTextReader.

I don't know the exact structure of the OP's json strings, so I'll assume something like this:

[
  {
    "context": {
      "id": 10
    }
  },
  {
    "context": {
      "id": 20
    }
  },
  {
    "context": {
      "id": 30
    }
  }
]

Result would be an integer array with the id values (10, 20, 30).

Parsing method

So this is the method that takes a JRaw object as a parameter and extracts the Ids, using a JsonTextReader.

private static IEnumerable<int> GetIds(JRaw raw)
{
    using (var stringReader = new StringReader(raw.Value.ToString()))
    using (var textReader = new JsonTextReader(stringReader))
    {
        while (textReader.Read())
        {
            if (textReader.TokenType == JsonToken.PropertyName && textReader.Value.Equals("id"))
            {
                int? id = textReader.ReadAsInt32();

                if (id.HasValue)
                {
                    yield return id.Value;
                }
            }
        }
    }
}

In the above example I'm assuming there is one and only one type of object with an id property.

There are other ways to extract the information we need - e.g. we can check the token type and the path as follows:

if (textReader.TokenType == JsonToken.Integer && textReader.Path.EndsWith("context.id"))
{
    int id = Convert.ToInt32(textReader.Value);
    yield return id;
}

Testing the code

I created the following C# classes that match the above json structure, for testing purposes:

public class Data
{
    [JsonProperty("context")]
    public Context Context { get; set; }

    public Data(int id)
    {
        Context = new Context
        {
            Id = id
        };
    }
}

public class Context
{
    [JsonProperty("id")]
    public int Id { get; set; }
}

Creating a JRaw object and extracting the Ids:

class Program
{
    static void Main(string[] args)
    {
        JRaw rawJson = CreateRawJson(); 
        List<int> ids = GetIds(rawJson).ToList(); 

        Console.Read();
    }

    //  Instantiates 1 million Data objects and then creates a JRaw object
    private static JRaw CreateRawJson()
    {
        var data = new List<Data>();

        for (int i = 1; i <= 1_000_000; i++)
        {
            data.Add(new Data(i));
        }

        string json = JsonConvert.SerializeObject(data);

        return new JRaw(json);
    }
}

Memory Usage

Using Visual Studio's Diagnostic tools I took the following snapshots, to check the memory usage:

Memory Usage screenshot

  • Snapshot #1 was taken at the beginning of the console application (low memory as expected)
  • Snapshot #2 was taken after creating the JRaw object

    JRaw rawJson = CreateRawJson();

  • Snapshot #3 was taken after extracting the ids

    List ids = GetIds(rawJson).ToList();

Rui Jarimba
  • 11,166
  • 11
  • 56
  • 86