0

I need to be able to apply Math.Round to every Number in a JSON document. I don't have any control over the original writer of the JSON document so my input is a string. The numbers can be within JSON objects themselves which in turn could be within JsonArray so ideally I'd like to do it recursively.

I have tried to knock up something recursive but find I am struggling with JsonElement vs JsonProperty when diving down through the JSON so have resorted to only supporting the first tier of objects in the tree. Here is my (non recursive) code that only works on objects:

public static string ApplyRounding(string originalJson)
{
    using var ms = new MemoryStream();
    using var writer = new Utf8JsonWriter(ms, new JsonWriterOptions{ Indented = false, SkipValidation = true});
    using var jsonDocument = JsonDocument.Parse(originalJson);

    var root = jsonDocument.RootElement;
    
    if (root.ValueKind == JsonValueKind.Object)
    {
        writer.WriteStartObject();
    }
    else
    {
        return string.Empty;
    }

    foreach (var property in root.EnumerateObject())
    {
        if (property.Value.ValueKind == JsonValueKind.Object)
        {
            writer.WritePropertyName(property.Name);
            writer.WriteStartObject();
            foreach (var innerProperty in property.Value.EnumerateObject())
            {
                if (innerProperty.Value.ValueKind == JsonValueKind.Number)
                {
                    writer.WriteRoundedNumber(innerProperty, 15);
                }
                else
                {
                    innerProperty.WriteTo(writer);
                }
            }

            writer.WriteEndObject();
        }
        else if (property.Value.ValueKind == JsonValueKind.Number)
        {
            writer.WriteRoundedNumber(property, 15);
        }
        else
        {
            property.WriteTo(writer);
        }
    }
    writer.WriteEndObject();
    writer.Flush();
            
    return Encoding.UTF8.GetString(ms.ToArray());
}

I am using C# 7 so am aware of the JsonNode type supporting in place editing of JSON but couldn't find a nice way to navigate that. I feel that this should be easier than it is, have I missed something?

Dutts
  • 5,781
  • 3
  • 39
  • 61

2 Answers2

2

I would argue that JsonNode API is a bit worse then similar API from Newtonsoft.Json but you can make it work too. Something to start with:

public static string ApplyRounding(string originalJson)
{
    var jsonNode = JsonNode.Parse(originalJson);
    
    if (jsonNode is JsonObject jsonObject)
    {
        HandleObject(jsonObject);
    }

    return jsonNode.ToJsonString();
    
    static void HandleObject(JsonObject jsonObject)
    {
        JsonNode jsonNode;
        foreach (var prop in jsonObject.ToList()) // need to create new collection so will not need to change existing one. Possibly filter out only the nodes which will require modification
        {
            if (prop.Value is JsonValue value)
            {
                if (value.TryGetValue(out long l)) // have not found other way to check the value type
                {
                    // no need to round
                }
                else if (value.TryGetValue(out double d))
                {
                    jsonObject[prop.Key] = JsonValue.Create(42.0); // apply rounding here
                }
            }
            else if (prop.Value is JsonObject innerObject)
            {
                HandleObject(innerObject); // recursive handling. Possibly consider recursion limit or move into queue.
            }
        }
    }
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
1

This is what I did in the end, I didn't use the JsonNode as I agree it's a bit clunky.

internal static string ApplyRounding(string originalJson, int digits = 14)
{
    using var ms = new MemoryStream();
    using var writer = new Utf8JsonWriter(ms, new JsonWriterOptions{ Indented = false, SkipValidation = true});
    using var jsonDocument = JsonDocument.Parse(originalJson);

    var root = jsonDocument.RootElement;
    
    TraverseJson(root, writer, digits);

    writer.Flush();
    var doc = Encoding.UTF8.GetString(ms.ToArray());
    return doc;
}


private static void TraverseJson(JsonElement element, Utf8JsonWriter writer, int digits)
{
    switch (element.ValueKind)
    {
        case JsonValueKind.Object:
            writer.WriteStartObject();
            foreach (var property in element.EnumerateObject())
            {
                writer.WritePropertyName(property.Name);
                TraverseJson(property.Value, writer, digits);
            }
            writer.WriteEndObject();
            break;
        case JsonValueKind.Array:
            writer.WriteStartArray();
            foreach (var item in element.EnumerateArray())
            {
                TraverseJson(item, writer, digits);
            }
            writer.WriteEndArray();
            break;
        case JsonValueKind.Number:
            writer.WriteRoundedNumber(element, digits);
            break;
        default:
            element.WriteTo(writer);
            break;
    }
}

public static void WriteRoundedNumber(this Utf8JsonWriter writer, JsonElement element, int digits)
{
    var roundedValue = Math.Round(element.GetDouble(), digits);
    writer.WriteNumberValue(roundedValue);
}
Dutts
  • 5,781
  • 3
  • 39
  • 61