so <@UQ8K13T7X> having issues with deserialization...
# contribute
b
so @lemon-agent-27707 having issues with deserialization again. After a stack runs an UP command, it hits the CLI with
stack output --json
in order to get the latest outputs. Specifically this function:
Copy code
async outputs(): Promise<OutputMap> {
    await this.workspace.selectStack(this.name);
    // TODO: do this in parallel after this is fixed <https://github.com/pulumi/pulumi/issues/6050>
    const maskedResult = await this.runPulumiCmd(["stack", "output", "--json"]);
    const plaintextResult = await this.runPulumiCmd(["stack", "output", "--json", "--show-secrets"]);
    const maskedOuts = JSON.parse(maskedResult.stdout);
    const plaintextOuts = JSON.parse(plaintextResult.stdout);
    const outputs: OutputMap = {};

    for (const [key, value] of Object.entries(plaintextOuts)) {
        const secret = maskedOuts[key] === secretSentinel;
        outputs[key] = { value, secret };
    }

    return outputs;
}
I'm willing to take any suggestions on how you'd like to accomplish this same result in .NET, since we can't simply assign arbitrary JSON to an anonymous object and have it typed such that we can iterate over the properties to build the resulting dictionary. I would consider just returning the JSON and letting the consumer deal with it and whatever they want to do with it. But that means you're losing the
IsSecret
boolean on your output map as well, because we would only be able to return either plaintext or masked.
Is the JSON received from
stack output
a only ever 1 layer deep, or can it return nested objects? If it's only ever 1 layer deep and I can return all the values as strings than that may be fine.
l
FWIW, the go implementation uses dynamic typing to accomplish this (interface{}) and it's fairly painful to consume as a result. Here's an example of the type assertions required to consume outputs in the Go Automation API: https://github.com/pulumi/halloumi/blob/main/go/pkg/orchestrator/orchestrator.go#L132-L139 I wouldn't be surprised if it was similarly messy to consume outputs in dotnet. cc @tall-librarian-49374
t
JSON may contain nested objects. Would something like this work? https://stackoverflow.com/a/47046191/1171619
b
you mean specifically that the result of
stack output
can return nested objects? Up until this point I have been using
System.Text.Json
not newtonsoft so I would have to switch.
I can do that, it will just take a bit cause I have to migrate all the existing serialization code
t
stack output
 can return nested objects?
yes
I have to migrate all the existing serialization code
Isn’t a similar trick doable with System.Text.Json? It’s just an old question that I picked.
b
unfortunately if you deserialize to
Dictionary<string, object>
in System.Text.Json what you get back is
Dictionary<string, JsonElement>
apparently I can just write a converter to add the same behavior https://github.com/dotnet/runtime/issues/29960#issuecomment-504125143
ok so ill do that
well their example only supports booleans, I would need to add the guesswork that they don't want to add for all the other primitives. so if we want to go that route might be better to go to newtonsoft I guess so we're not reinventing the wheel. I had been trying to avoid adding dependencies to your lib
t
It sounds wrong to switch dependencies because of that. Maybe assume
Dictionary<string, string>
for now and leave a todo to write a recursive conversion
b
well
System.Text.Json
is throwing me an exception if I try to deserialize JSON that has nested objects to
Dictionary<string, string>
. It doesn't just place the raw string in the value it just fails
can one of you explain the
secretSentinel
comparison that is happening in the code above?
t
b
Oh perfect I don’t know why I didn’t see that. Thank you.
l
Yup, a good way to reverse engineer some of this stuff is to just poke around with a dev stack and the CLI commands that are invoked.
t
@bored-oyster-3147 Why is
Dictionary<string, JsonElement>
not enough? From JsonElement, it should be possible to inspect it's type (i.e. JObject, JArray, JValue).
b
well, first of all
JObject
,
JArray
, &
JValue
are all Newtonsoft types, not System.Text.Json. System.Text.Json uses
JsonElement
to universally describe all JSON tokens. If it is a nested object, than the value will be
JsonElement
with a
TokenType == Object
. And I would need to figure out how to get some kind of instance out of that so that I can return an
OutputValue
. Our
OutputValue
looks like this:
Copy code
{
    object Value,
    bool IsSecret
}
And we don't want to expose
JsonElement
. meaning we shouldn't be returning an
OutputValue
where
OutputValue.Value
at runtime is
JsonElement
. With newtonsoft, If I deserialize to
Dictionary<string, object>
than if it is a nested object it will just be another
Dictionary<string, object>
, recursively, until completion. Meaning
OutputValue.Value
at runtime will be
Dictionary<string, object>
rather than exposing a serialization type
But if you want the
IsSecret
property, wouldn't we want to return
Dictionary<string, OutputMap>
?
b
we do return
Dictionary<string, OutputValue>
. checkout the function here The converter you linked is interesting, but it is also not a public converter - it is only used for some tests temporarily. It also isn't recursively returning dictionaries if the value is a complex object. It also falls back to
JsonElement
if it cant parse the type... which means we would possibly be returning
JsonElement
t
It's just a sample. We can copy it and change it however we want. I'd be willing to work on this.
b
the issue is the reinvention of the wheel, why try to come up with a converter that exhaustively handles all possible conversions and then needs to test all those possibilities if newtonsoft is already doing that and testing it?
System.Text.Json happened to make the design decision that they don't ever want to try to guess the type, noted https://github.com/dotnet/runtime/issues/29960#issuecomment-504125143
t
Yeah, I'm just surprised no one has created a full converter yet. Json.Net is kind of dead (or dying). The author works for Microsoft on
System.Text.Json
now.
b
hmmm i don't think so? I mean I know he works at that but there has been no kind of update that JSON.Net will be abandoned anytime soon. Do you have a blog post saying as much?
t
I guess it’s just an assumption for how Microsoft works with open source 😉
t
Not explicitly, but there hasn't been a release in over a year, there have only been 9 commits since 2019 and then there's this comment where someone says "this repo is (almost) dead", and James does not correct him.
From James's perspective, why would he want to work on two competing products? That would drive me nuts. lol
Also, I found a more comprehensive and tested converter.
b
alright i'm hearing ya'll. I actually just wrote this:
Copy code
private class SystemObjectJsonConverter : JsonConverter<object>
        {
            public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                if (reader.TokenType == JsonTokenType.True)
                {
                    return true;
                }

                if (reader.TokenType == JsonTokenType.False)
                {
                    return false;
                }

                if (reader.TokenType == JsonTokenType.Number)
                {
                    if (reader.TryGetInt64(out long l))
                    {
                        return l;
                    }

                    return reader.GetDouble();
                }

                if (reader.TokenType == JsonTokenType.String)
                {
                    if (reader.TryGetDateTime(out DateTime datetime))
                    {
                        return datetime;
                    }

                    return reader.GetString();
                }

                if (reader.TokenType == JsonTokenType.StartArray)
                {
                    return JsonSerializer.Deserialize<object[]>(ref reader, options);
                }

                if (reader.TokenType == JsonTokenType.StartObject)
                {
                    var dictionary = new Dictionary<string, object>();

                    reader.Read();
                    while (reader.TokenType != JsonTokenType.EndObject)
                    {
                        if (reader.TokenType != JsonTokenType.PropertyName)
                            throw new JsonException("Expecting property name.");

                        var propertyName = reader.GetString();

                        reader.Read();
                        var value = JsonSerializer.Deserialize<object>(ref reader, options);
                        dictionary[propertyName] = value;

                        reader.Read();
                    }

                    return dictionary;
                }

                throw new JsonException("Invalid JSON element.");
            }

            public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
            {
                throw new NotSupportedException($"Writing as [{typeof(object).FullName}] is not supported.");
            }
        }
which seems to work. could just that and stick with System.Text.Json if that's what we want to do
I definitely would prefer to have 1 less dependency I guess I was thinking it was a more complex thing