Josh
Josh

Reputation: 598

Deserialize JSON without knowing full structure

I'm redoing the backend of a very basic framework that connects to a completely customizable frontend. It was originally in PHP but for the refactor have been plodding away in F#. Although it seems like PHP might be the more suited language. But people keep telling me you can do everything in F# and I like the syntax and need to learn and this seemingly simple project has me stumped when it comes to JSON. This is a further fleshed out version of my question yesterday, but it got alot more complex than I thought.

Here goes.

The frontend is basically a collection of HTML files, which are simply loaded in PHP and preg_replace() is used to replace things like [var: varName] or [var: array|key] or the troublesome one: [lang: hello]. That needs to be replaced by a variable defined in a translation dictionary, which is stored as JSON which is also editable by a non-programmer.

I can't change the frontend or the JSON files, and both are designed to be edited by non-programmers so it is very likely that there will be errors, calls to language variables that don't exist etc.

So we might have 2 json files, english.json and french.json

english.json contains:

{
   "hello":"Hello",
   "bye":"Goodbye"
}

french.json:

{
"hello": "Bonjour",
"duck": "Canard"
//Plus users can add whatever else they want here and expect to be able to use it in a template
}

There is a template that contains

<b>[lang: hello]</b>

<span>Favourite Animal: [lang:duck]</span>

In this case, if the language is set to "english" and english.json is being loaded, that should read:

<b>Hello</b>

<span>Favourite Animal: </span>

Or in French:

<b>Bonjour</b>

<span>Favourite Animal: Canard</span>

We can assume that the json format key: value is always string:string but ideally I'd like to handle string: 'T as well but that might be beyond the scope of this question.

So I need to convert a JSON file (called by dynamic name, which gave F# Data a bit of an issue I couldn't solve last night as it only allowed a static filename as a sample, and since these two files have potential to be different from sample and provided, the type provider doesn't work) to a dictionary or some other collection.

Now inside the template parsing function I need to replace [lang: hello] with something like

let key = "duck"

(*Magic function to convert JSON to usable collection*)

let languageString = convertedJSONCollection.[key] (*And obviously check if containsKey first*)

Which means I need to call the key dynamically, and I couldn't figure out how to do that with the type that FSharp.Data provided.

I have played around with some Thoth as well to some promising results that ended up going nowhere. I avoided JSON.NET because I thought it was paid, but just realised I am mistaken there so might be an avenue to explore

For comparison, the PHP function looks something like this:

function loadLanguage($lang='english){
    $json = file_get_contents("$lang.json");
    return json_decode($json, true);
}

$key = 'duck';

$langVars = loadLanguage();
$duck = $langVars[$key] || "";

Is there a clean way to do this in F#/.NET? JSON seems really painful to work with in comparison to PHP/Javascript and I'm starting to lose my mind. Am I going to have to write my own parser (which means probably going back to PHP)?

Cheers to all you F# geniuses who know the answer :p

Upvotes: 1

Views: 317

Answers (1)

user1981
user1981

Reputation: 586

open Thoth.Json.Net
let deserialiseDictionary (s: string) =
    s
    |> Decode.unsafeFromString (Decode.keyValuePairs Decode.string)
    |> Map.ofList

let printDictionary json =
    json
    |> deserialiseDictionary
    |> fun m -> printfn "%s" m.["hello"] // Hello

For the question about 'T the question becomes, what can 'T be? For json it very limited, it can be a number of things, string, json-object, number, bool or json array. What should happen if it is bool or a number?

Upvotes: 2

Related Questions