#Handle null values in struct that is dynamically filled

1 messages · Page 1 of 1 (latest)

stark blade
#

I have json objects that fill a struct with values, and when iterating over some of them, i noticed one that was meant to be a string (or []const u8) had came back as null-- not the string "null" but just null, and i am unsure how to handle that, as any of the members of this struct could potentially come back as null. This causes a runtime error rather than a compiler error because it is coming from an http request that is dynamic.

this is my for loop iterating over the items:

    for (rresults.items) |val| {
        var current_result = Result{
            .Description = val.object.get("Description").?.string,
            .FirstSubmitted = val.object.get("FirstSubmitted").?.integer,
            .ID = val.object.get("ID").?.integer,
            .LastModified = val.object.get("LastModified").?.integer,
            .Maintainer = val.object.get("Maintainer").?.string,
            .Name = val.object.get("Name").?.string,
            .NumVotes = val.object.get("NumVotes").?.integer,
            .PackageBase = val.object.get("PackageBase").?.string,
            .PackageBaseID = val.object.get("PackageBaseID").?.integer,
            .Popularity = val.object.get("Popularity").?.float,
            .URL = val.object.get("URL").?.string,
            .URLPath = val.object.get("URLPath").?.string,
            .Version = val.object.get("Version").?.string,
        };

        // code below here is a statement printing each field of the struct to stdout
        // however it makes the post too many characters to post.
    }

and this is the struct definition:

const Result = struct {
    Description: []const u8,
    FirstSubmitted: i64,
    ID: i64,
    LastModified: i64,
    Maintainer: []const u8,
    Name: []const u8,
    NumVotes: i64,
    PackageBase: []const u8,
    PackageBaseID: i64,
    Popularity: f64,
    URL: []const u8,
    URLPath: []const u8,
    Version: []const u8,
};
crude igloo
#

you could do something like

var current_result = Result{
  .Description = blk: {
    if (val.object.get("Description")) |desc| {
      break :blk desc.string;
    } else {
      break :blk "Default Description";
      // or if you want bubble up an error on a null value
      // instead of providing a default:
      // return error.NullResultItem;
    }
  },
  .FirstSubmitted = // etc...
}
#

a one liner would be:

.Description = if (val.object.get("Description")) |desc| desc.string else "Default Description",
stark blade
#

so you can write it without the block declaration?

crude igloo
#

yeah second one works just as well, blocks just give you more freedom with how many lines you want to use to handle it

stark blade
#

thank you so much this was very helpful!

crude igloo
#

no problem!

#

also the .? operator should only be used on stuff youre sure will never be null

#

an std.debug.panic is much better for crashing on null

stark blade
# crude igloo no problem!

actually, I tried implenting this (using the oneliner style) and it still crashed when there was a null field for maintainer, with the same error message, saying thread panic accessing union 'string' when field 'null' is active

crude igloo
#

oh what is the return type of .get()?

stark blade
#

for maintainer it expects a string

crude igloo
#

oh like do you have the definition of what type it returns

stark blade
#

dynamic json value

crude igloo
#

from what i can tell you might have to do

var current_result = Result{
  .Description = blk: {
    if (val.object.get("Description")) |desc| {
      switch (desc) {
        .string => |str| break :blk str,
        .null => break :blk "Default Description",
        else => return error.WrongJsonType,
      }
    } else {
      break :blk "Default Description";
      // or if you want bubble up an error on a null value
      // instead of providing a default:
      // return error.NullResultItem;
    }
  },
  .FirstSubmitted = // etc...
}
stark blade
#

ahh i see, ill give this a try

crude igloo
#

probably also best to turn this into a helper func with how many fields you have to fill lol

stark blade
#

this doesnt seem to be working, it makes it so that i am unable to use the values in format strings, as they would need to be {any} instead of {s} @crude igloo

crude igloo
#

whats the error youre getting?

stark blade
#
/home/wbr/.zvm/master/lib/std/fmt.zig:499:17: error: cannot format optional without a specifier (i.e. {?} or {any})
                @compileError("cannot format optional without a specifier (i.e. {?} or {any})");
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
crude igloo
#

ah, whats the line look like where youre printing?

#

well, as many lines as youd need for context too

stark blade
#
for (rresults.items) |val| {
  var current_result = Result{
    .Description = if (val.object.get("Description")) |desc| desc.string else "Default Description",
    .FirstSubmitted = if (val.object.get("FirstSubmitted")) |fsub| fsub.integer else 0,
    .ID = if (val.object.get("ID")) |id| id.integer else 0,
    .LastModified = val.object.get("LastModified").?.integer,
    .Maintainer = blk: {
      if (val.object.get("Maintainer")) |mntr| {
        switch (mntr) {
          .string => |str| break :blk str,
          .null => break :blk "Default Maintainer",
          else => return error.WrongJsonType,
        }
      }
    },
    .Name = val.object.get("Name").?.string,
    .NumVotes = val.object.get("NumVotes").?.integer,
    .PackageBase = val.object.get("PackageBase").?.string,
    .PackageBaseID = val.object.get("PackageBaseID").?.integer,
    .Popularity = blk: {
      if (val.object.get("Popularity")) |pop| {
        switch (pop) {
          .float => |flt| break :blk flt,
          .null => break :blk 0.0,
          else => return error.WrongJsonType,
        }
      }
    },
    .URL = val.object.get("URL").?.string,
    .URLPath = val.object.get("URLPath").?.string,
    .Version = val.object.get("Version").?.string,
  };

  try out.print("Description: {s}\nFirstSubmitted: {d}\nID: {d}\nLastModified: {d}\nMaintainer: {s}\nName: {s}\nNumVotes: {d}\nPackageBase: {s}\nPackageBaseID: {d}\nPopularity: {d}\nURL: {s}\nURLPath: {s}\nVersion: {s}\n", .{ current_result.Description, current_result.FirstSubmitted, current_result.ID, current_result.LastModified, current_result.Maintainer, current_result.Name, current_result.NumVotes, current_result.PackageBase, current_result.PackageBaseID, current_result.Popularity, current_result.URL, current_result.URLPath, current_result.Version });
}
#

i started making a block for .Popularity as well because it would sometimes come back as an integer rather than a float, and that would cause it to crash at runtime, but i couldnt figure that out either.

#

also, out is std.io.getStdOut().writer()

crude igloo
#

from what i can tell none of Result's fields are optional, so i dont see why it would complain

#

do you print anywhere else? maybe try commenting out that line and see if the error is still there

stark blade
#

ununsed local for the struct then lol

crude igloo
#

you can do _ = current_result;

#

you can also use -freference-trace to see where the error is coming from

stark blade
#
-> zig build run -freference-trace
error: WrongJsonType
/home/wbr/projects/kaylee/src/main.zig:102:33: 0x33d669 in main (kaylee)
                        else => return error.WrongJsonType,
                                ^
run kaylee: error: the following command exited with error code 1:
/home/wbr/projects/kaylee/zig-out/bin/kaylee 
Build Summary: 3/5 steps succeeded; 1 failed (disable with -fno-summary)
run transitive failure
└─ run kaylee failure
   ├─ zig build-exe kaylee Debug native cached 6ms MaxRSS:34M
   └─ install cached
      └─ install kaylee cached
         └─ zig build-exe kaylee Debug native (reused)
error: the following build command failed with exit code 1:
/home/wbr/projects/kaylee/zig-cache/o/5bac71fb8ed78cac27043834ec5b348f/build /home/wbr/.zvm/master/zig /home/wbr/projects/kaylee /home/wbr/projects/kaylee/zig-cache /home/wbr/.cache/zig run -freference-trace -freference-trace
crude igloo
#

oh is that the popularity block? you can do

    .Popularity = blk: {
      if (val.object.get("Popularity")) |pop| {
        switch (pop) {
          .float => |flt| break :blk flt,
          .integer => |int| break :blk @intToFloat(f64, int),
          .null => break :blk 0.0,
          else => return error.WrongJsonType,
        }
      }
#

any type that you dont have a case for will hit the else block which returns that error

stark blade
#

okay, it made it to the URL field and hit a null, going to make a case for that and see from there

crude igloo
#

you should also have a case for if the object.get returns null, like:

    .Popularity = blk: {
      if (val.object.get("Popularity")) |pop| {
        switch (pop) {
          .float => |flt| break :blk flt,
          .integer => |int| break :blk @intToFloat(f64, int),
          .null => break :blk 0.0,
          else => return error.WrongJsonType,
        }
      } else {
        break :blk 0.0;
        // or
        return error.FieldNotFound;
      }
stark blade
#

okay, it compiled and ran with no errors, however the "cannot format optional" error still remains if i was trying to print, and after going through to see which field this was happening with, it is the .Maintainer field.

crude igloo
#

from your code the type of Result.Maintaner is just []const u8, which should be fine, but you can also do {?s} to print an optional string

stark blade
#

sorry, i am a bonehead and noticed in the struct definition that i had accidently made maintainer optional. lol.

crude igloo
#

ohhhh np lol

stark blade
#

thank you again so much for your help