Logo

dev-resources.site

for different kinds of informations.

Code. Gleam. Extract fields from JSON

Published at
12/18/2024
Categories
gleamlang
json
webdev
Author
axkira
Categories
3 categories in total
gleamlang
open
json
open
webdev
open
Author
6 person written this
axkira
open
Code. Gleam. Extract fields from JSON

Hi!
Sometimes we need to get just a couple of fields from a JSON string without any complex stuff like writing decoders or using a JSON-schema.

We will use result.then() for easy piping: if the result, passed to then() is Ok, then we continue with it; if the result is Error - we stop there and return this error from the pipe. So it's important to have consistent Error types through the whole pipe.

So, here are the steps.

# Erlang version <= OTP26
gleam add gleam_json@1

# Erlang version >= OTP27
gleam add gleam_json@2
Enter fullscreen mode Exit fullscreen mode
  • Write some types in types.gleam:
pub type HttpError {
  // ...
  JsonParseError(error: JsonParseErrorType, field: String, json_string: String)
}

pub type JsonParseErrorType {
  InvalidJsonForParsing
  ObjectFieldNotFound
  IntegerFieldNotFound
  FloatFieldNotFound
  StringFieldNotFound
  SeveralFieldsNotFound
}
Enter fullscreen mode Exit fullscreen mode
  • Write some very basic utils to shorten the gleam_json usage syntax and simplify piping process. Here I've added only some funs for parsing root JSON as an object (it can also be an array) and extracting integers, floats and strings (it won't be hard to add other ones by yourself):
import gleam/string
import gleam/result.{then, replace_error}
import gleam/json
import gleam/dynamic
import gleam/dict
import types.{
  type HttpError, JsonParseError,
  InvalidJsonForParsing,
  ObjectFieldNotFound,
  IntegerFieldNotFound,
  FloatFieldNotFound,
  StringFieldNotFound,
}


/// Parses JSON string as an object into a dictionary.
pub fn parse_obj(json_string: String) -> Result(dict.Dict(String, dynamic.Dynamic), HttpError) {
  json_string
  |> json.decode(dynamic.dict(dynamic.string, dynamic.dynamic))
  |> replace_error(JsonParseError(error: InvalidJsonForParsing, field: "", json_string: json_string))
}

/// Retrieves an object field from the current JSON level.
pub fn get_obj(
  body: dict.Dict(String, dynamic.Dynamic),
  field: String,
) -> Result(dict.Dict(String, dynamic.Dynamic), HttpError) {
  body
  |> dict.get(field)
  |> then(as_dict())
  |> replace_error(JsonParseError(error: ObjectFieldNotFound, field: field, json_string: string.inspect(body)))
}

/// Retrieves an integer field from the current JSON level.
pub fn get_int(
  body: dict.Dict(String, dynamic.Dynamic),
  field: String,
) -> Result(Int, HttpError) {
  body
  |> dict.get(field)
  |> then(as_int())
  |> replace_error(JsonParseError(error: IntegerFieldNotFound, field: field, json_string: string.inspect(body)))
}

/// Retrieves a float field from the current JSON level.
pub fn get_float(
  body: dict.Dict(String, dynamic.Dynamic),
  field: String,
) -> Result(Float, HttpError) {
  body
  |> dict.get(field)
  |> then(as_float())
  |> replace_error(JsonParseError(error: FloatFieldNotFound, field: field, json_string: string.inspect(body)))
}

/// Retrieves a string field from the current JSON level.
pub fn get_string(
  body: dict.Dict(String, dynamic.Dynamic),
  field: String,
) -> Result(String, HttpError) {
  body
  |> dict.get(field)
  |> then(as_string())
  |> replace_error(JsonParseError(error: StringFieldNotFound, field: field, json_string: string.inspect(body)))
}

/// Replacement for `dynamic.dict(dynamic.string, dynamic.dynamic)` to have a custom `Error` for piping.
fn as_dict() -> fn(dynamic.Dynamic) -> Result(dict.Dict(String, dynamic.Dynamic), Nil) {
  fn(body: dynamic.Dynamic) {
    body
    |> dynamic.dict(dynamic.string, dynamic.dynamic)
    |> replace_error(Nil)
  }
}

/// Replacement for `dynamic.int(_)` to have a custom `Error` for piping.
fn as_int() -> fn(dynamic.Dynamic) -> Result(Int, Nil) {
  fn(body: dynamic.Dynamic) {
    body
    |> dynamic.int()
    |> replace_error(Nil)
  }
}

/// Replacement for `dynamic.float(_)` to have a custom `Error` for piping.
fn as_float() -> fn(dynamic.Dynamic) -> Result(Float, Nil) {
  fn(body: dynamic.Dynamic) {
    body
    |> dynamic.float()
    |> replace_error(Nil)
  }
}

/// Replacement for `dynamic.string(_)` to have a custom `Error` for piping.
fn as_string() -> fn(dynamic.Dynamic) -> Result(String, Nil) {
  fn(body: dynamic.Dynamic) {
    body
    |> dynamic.string()
    |> replace_error(Nil)
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Use your new utils:
pub fn main() {
  // {
  //   "name": "Lucy",
  //   "stats": {
  //     "class": "Barbarian",
  //     "power": 6,
  //     "max_hp": 10
  //   },
  //   "pets": {
  //     "Wolfie": {
  //       "type": "dog"
  //     }
  //   }
  // }
  let json_string = "{\"name\": \"Lucy\",\"stats\": {\"class\": \"Barbarian\",\"power\": 6,\"max_hp\": 10},\"pets\": {\"Wolfie\": {\"type\": \"dog\"}}}"
  let json_dict = json_string |> json.parse_obj()

  // Get Lucy's name
  json_dict
  |> then(json.get_string(_, "name"))
  |> io.debug()
  // Ok("Lucy")

  // Get Wolfie's type
  json_dict
  |> then(json.get_obj(_, "pets"))
  |> then(json.get_obj(_, "Wolfie"))
  |> then(json.get_string(_, "type"))
  |> io.debug()
  // Ok("dog")

  // Get something ridiculous
  // Note that we get an error on extracting the `nonsense` field and don't go to `type` out of the box
  json_dict
  |> then(json.get_obj(_, "stats"))
  |> then(json.get_obj(_, "nonsense"))
  |> then(json.get_string(_, "type"))
  |> io.debug()
  // Error(JsonParseError(ObjectFieldNotFound, "nonsense", "dict.from_list([#(\"class\", \"Barbarian\"), #(\"max_hp\", 10), #(\"power\", 6)])"))

  // Get something even more ridiculous
  // Now we handle both fields and raising a `SeveralFieldsNotFound` error
  let nonesense =
  json_dict
  |> then(json.get_obj(_, "stats"))
  |> then(json.get_string(_, "nonesense"))

  let delirious =
  json_dict
  |> then(json.get_obj(_, "stats"))
  |> then(json.get_int(_, "delirious"))

  case nonesense, delirious {
    Ok(nonesense), Ok(delirious) -> Ok(#(nonesense, delirious))
    Error(_), Error(_) -> Error(JsonParseError(error: SeveralFieldsNotFound, field: "stats.{nonesense, delirious}", json_string: json_string))
    Error(err), _ -> Error(err)
    _, Error(err) -> Error(err)
  }
  |> io.debug()
  // Error(JsonParseError(SeveralFieldsNotFound, "stats.{nonesense, delirious}", "{\"name\": \"Lucy\",\"stats\": {\"class\": \"Barbarian\",\"power\": 6,\"max_hp\": 10},\"pets\": {\"Wolfie\": {\"type\": \"dog\"}}}"))
}
Enter fullscreen mode Exit fullscreen mode

Bye!


P.S.
Sorry for poor syntax highlighting. Gleam isn't supported and I've chosen Erlang. That's better than a plain text, I guess πŸ€·β€β™‚οΈ

json Article's
30 articles in total
Favicon
How to Fetch URL Content, Set It into a Dictionary, and Extract Specific Keys in iOS Shortcuts
Favicon
Dynamic Routes in Astro (+load parameters from JSON)
Favicon
Effortlessly Host Static JSON Files with JSONsilo.com
Favicon
How to Implement Authentication in React Using JWT (JSON Web Tokens)
Favicon
Converting documents for LLM processing β€” A modern approach
Favicon
Import JSON Data in Astro (with Typescript)
Favicon
Devise not accepting JSON Token
Favicon
Integration for FormatJS/react-intl: Automated Translations with doloc
Favicon
β€œDefu” usage in unbuild source code.
Favicon
Converting documents for LLM processing β€” A modern approach
Favicon
How to crawl and parse JSON data with Python crawler
Favicon
JSON Visual Edit
Favicon
Develop a ulauncher extension with a command database
Favicon
Building a Smart Feedback Agent with Copilot Studio, Adaptive cards and Power Automate
Favicon
Simplifying JSON Validation with Ajv (Another JSON Validator)
Favicon
A Straightforward Guide to Building and Using aΒ JSON Database File
Favicon
AI prompt sample - a full chat content that demonstrates how to get a professional looking website in a few munities
Favicon
Fixing and Validating JSON with Ease: An In-Depth Guide
Favicon
Useful too to work with your JSON files - jq
Favicon
what is jq? a program for json files
Favicon
Code. Gleam. Extract fields from JSON
Favicon
Build an Application Without SQL Server Database (Avoiding RPrometheusedis, MongoDB, and )
Favicon
FAQ β€” Bloomer Mock Data Generator
Favicon
My React Journey: Day 18
Favicon
Working with JSON in MySQL
Favicon
JSON for Biggners
Favicon
angular and json
Favicon
iter.json: A Powerful and Efficient Way to Iterate and Manipulate JSON in Go
Favicon
This unknown Currency API is served over 50 Billion times a month !
Favicon
Common Data Formats in JavaScript: A Comprehensive Guide With Examples

Featured ones: