Function accepting only json as argument, with guard?

Is it possible to create a function to accept only a valid json as parameter/argument ? Not map!
Precisely, I need something that would perform as:

def myFn(obj) when is_json(obj) do
...function content...
end

I am aware that is_json() does not exist in Elixir.
The idea is the function should accept only a string that will parse to json:
myFn("{\"k1\":7,"\k2\":"\hello\"}") should be valid but myFn("bla bla bla") not. (the json objects arrive already stringified from an upstream API)

1 Like

Use Jason.decode! to throw or Jason.decode and pattern match on the result. Not in a guard though.

Or similar in whatever json library youā€™re using.

2 Likes

Might be a fine workaround, but I look for solving it with a guard as I consider it to be more elegant.

Guard clauses map to VM level instructions, you can only use those specific clauses in guards, in combinations. The only way to know that a string is JSON is to parse it, and I donā€™t see how you could create a set of guard clauses that would parse JSON.

5 Likes

I understand.
Iā€™ve just discovered that I cannot use a custom function as guard. I initially thought I could create my own is_json guard function ā€¦ :no_mouth:

Too bad Elixir does not have json as a native type. Json is an obliquus format, any modern language must support it natively.
Eg.: I am working with data feeds from industrial machines (automation robots) and their output standard format is json.

I understand your frustration, but I just have to ask the question:

What modern language has native support for JSON?

  • JavaScript has JSON.parse & JSON.stringify to decode & encode JSON via the std library

  • Python has json.loads & json.dumps to decode & encode JSON via import json

  • PHP has json_decode & json_encode to decode & encode JSON via the std library

  • Ruby has JSON.parse & JSON.generate to decode & encode JSON via require 'json'

  • ā€¦ [1]

The point Iā€™m trying to make is that there doesnā€™t appear to be a modern language that has ā€“ what I would be willing to call ā€“ native support for JSON.

Every widely used (modern?) language has functions or methods to encode & decode its native objects or data structures to/from JSON.

Even JavaScript (the ā€˜Jā€™ in JSON) doesnā€™t appear to have native support.

JSON (JavaScript Object Notation) is a lightweight data-interchange format [1], and in being such, I believe every language would need to attempt to parse JSON order to determine whether itā€™s valid or not, and Elixir is no exception. :slight_smile:

  1. json.org
8 Likes

You can, it only can use other guard safe functions.
You can create your own macro, or use defguard, defguardp,

2 Likes

JSON is just a format specification of a string, Elixir has native support for strings in any format, including JSON. To the extent that a language can parse a JSON formatted string to a native data structure, and marshal a native data structure to a string of JSON, it has the most ā€œsupport for JSONā€ possible.

Lamenting that a language doesnā€™t have ā€œa JSON native typeā€ is like lamenting that a language doesnā€™t have ā€œan HTML native typeā€.

1 Like

Validating JSON could be quite slow process depending how big is the string. If Iā€™m not mistaken all guards in Elixir execute in constant time. Meaning variable length JSON string canā€™t be a guard. Anyway you shouldnā€™t be validating JSON anywhere else but where the data comes into your system. As JSON being dynamic data, so just checking is JSON might not be enough if you are getting JSON from untrusted source like a browser.

2 Likes

That is not true. Few arenā€™t constant time:

  • length/1 is linear
  • is_map_key/2 and map access (map_get/2) are logarithmic (and dependants like is_struct/{1,2}
5 Likes

Interesting. So length is not stored in the list and it has to be iterated to get the length?

Yes, lists are singly linked lists and must be iterated to the end to find out the length. Thatā€™s why list == [] is recommended over length(list) == 0.

3 Likes

Not really in my case: A custom guard can only be defined based on existing guards.

[ā€¦] there doesnā€™t appear to be a modern language that has ā€“ what I would be willing to call ā€“ native support for JSON.

What do you think would Ballerinaā€™s json type qualify?
https://ballerina.io/learn/distinctive-language-features/data/#json-type

Valid JSON is also valid Ballerina syntax.

No, the json type is still an internal structure that any JSON string has to be parsed into, thereā€™s no automatic recognition of a string of JSON as JSON, ie. you still need to call: json foo = value:fromJsonString(the_string);.

I dont know if you can do it with a guard but you should be able to do pattern matching in the function clause like

def func(%JSON{} = my_json), do: my_json

That way the function will only match when a JSON struct is passed in.

Edit:

defmodule Test do
  defstruct hello: "world", field: 2
  
  def func(%Test{} = my_struct) do
    IO.puts("Test struct")
  end
end

%Test{hello: "world", field: 2}
|>Test.func()
# Test struct

%{hello: "world", field: 2}
|> Test.func()
# ** (FunctionClauseError) no function clause matching in Test.func/1