It is currently November 4th, 2024, 2:02 pm

Parsing JSON API

Tips and Tricks from the Rainmeter Community
stefansh
Posts: 3
Joined: January 22nd, 2020, 8:19 pm

Parsing JSON API

Post by stefansh »

I set out to write my first Rainmeter skin and found that people were using Perl Regular Expressions to parse JSON. This is problematic for a number of reasons:
  • JSON format doesn't guarantee ordering or spacing so an API is free to keep the same semantics but rearrange fields. This will randomly break regular expression-based skins.
  • Modification of and debugging non-trivial Regular Expressions is cumbersome, though there are some pretty cool tools for that.
  • The inherent complexity of Regular Expressions puts this type of development out of reach for a lot of people.
  • There are plenty of JSON parsing libraries that are well-tested and maintained and are performant.
Note: XML has most of the same problems and can be parsed using a similar method as mine

I searched for a solution and didn't find one so I came up with my own. Here's a fully-functional skin using this technique. The essence follows.

Given this excerpt of the Darksky weather API JSON response:

Code: Select all

{
    "timezone": "America/New_York",
    "currently": {
        "time": 1579389931,
        "summary": "Light Snow",
        "icon": "snow",
        "temperature": 31.06,
        "apparentTemperature": 25.12,
        "dewPoint": 29.5,
        "humidity": 0.94,
        "pressure": 1018.1,
        "visibility": 0.926,
        "ozone": 300
    },
    "hourly": {
        "summary": "Light rain until tomorrow morning.",
        "icon": "rain",
        "data": [
            {
                "time": 1579388400,
                "summary": "Light Snow",
                "icon": "snow",
                "precipIntensity": 0.0516,
            }
    }
}
Before (the old way)

weather.ini

Code: Select all

[Weather]
Measure=WebParser
URL=https://api.darksky.net/forecast/#APIKey#/#Location#
RegExp=(?siU).*"currently":\{.*"summary":"([^"]*)"[^}]*"icon":"([^"]*)"[^}]*"temperature":([^\.]*)(\.|,).*}[^}]*}.*,"hourly":{"summary":"([^"]*)"

[Summary]
Measure=WebParser
URL=[Weather]
StringIndex=1

[Icon]
Measure=WebParser
URL=[Weather]
StringIndex=2

[Temperature]
Measure=WebParser
URL=[Weather]
StringIndex=3

[Hourly]
Measure=WebParser
URL=[Weather]
StringIndex=5

[SummaryMeter]
Meter=String
MeasureName=Summary

[IconMeter]
Meter=Image
MeasureName=Icon
ImageName=%1.png

[TemperatureMeter]
Meter=String
MeasureName=Temperature

[HourlyMeter]
Meter=String
MeasureName=Hourly
After (the new way)

weather.ini

Code: Select all

[Weather]
Measure=WebParser
URL=https://api.darksky.net/forecast/#APIKey#/#Location#
RegExp=(?sU)^(.*)$
StringIndex=0
FinishAction=!UpdateMeasure ProcessData

[ProcessData]
Measure=Script
ScriptFile=#@#Scripts\weather.lua
UpdateDivider=-1

; Don't need measure sections :)

[Summary]
Meter=String

[Icon]
Meter=Image

[Temperature]
Meter=String

[Hourly]
Meter=String
weather.lua

Code: Select all

function Initialize()
	dofile( SKIN:GetVariable( '@' )..'Scripts\\json.lua' )
	weatherData = SKIN:GetMeasure( "Weather" )
end

function Update()
	data = decodeData( weatherData:GetStringValue() )

	SKIN:Bang( '!SetOption', 'Temperature', 'Text', data[ 'currently' ][ 'temperature' ] )
	SKIN:Bang( '!SetOption', 'Icon', 'ImageName', "#@#Images\\" .. data[ 'currently' ][ 'icon' ] )
	SKIN:Bang( '!SetOption', 'Summary', 'Text', data[ 'currently' ][ 'summary' ] )
	SKIN:Bang( '!SetOption', 'Hourly', 'Text', data[ 'hourly' ][ 'summary' ] )
end
json.lua

Get from https://github.com/rxi/json.lua

---

Questions and feedback are welcome.
User avatar
limitless
Posts: 76
Joined: January 8th, 2017, 2:31 am
Location: Charlotte, NC

Re: Parsing JSON API

Post by limitless »

This is awesome, cant wait to try it out!
Image
User avatar
n5dkj
Posts: 2
Joined: June 18th, 2024, 4:14 pm

Re: Parsing JSON API

Post by n5dkj »

I just found this and decided to try it out. It took me a while to make it work since I've never used LUA. Everything is working, but minor changes were needed to get it to run. My code looks like this:

scrape.lua

Code: Select all

function Initialize()
	json = dofile( SKIN:GetVariable( '@' )..'json.lua' )
	scraperData = SKIN:GetMeasure( "WebScraper" )
end

function Update()
	data = json.decode( scraperData:GetStringValue() )
	SKIN:Bang( '!SetOption', 'meterSunriseTime', 'Text', 'Sunrise: '..data[ 'sunrise' ] )
	SKIN:Bang( '!SetOption', 'meterSunSetTime', 'Text', 'Sunset: '..data[ 'sunset' ] )
	SKIN:Bang( '!SetOption', 'meterPowerballJackpot', 'Text', 'Jackpot: '..data[ 'pbjack' ] )
	SKIN:Bang( '!SetOption', 'meterPowerballCash', 'Text', 'Cash Value: '..data[ 'pbcash' ] )
	SKIN:Bang( '!SetOption', 'meterMegaMillionsJackpot', 'Text', 'Jackpot: '..data[ 'mmjack' ] )
	SKIN:Bang( '!SetOption', 'meterMegaMillionsCash', 'Text', 'Cash Value: '..data[ 'mmcash' ] )
end
I am getting an error logged:

Code: Select all

Script: json.lua:185: unexpected character '' at line 1 col 1
I am loading the JSON file at https://n5dkj.radio/time/webdata.json. The error does not keep the meters from updating correctly.
David K Johnson, 🗼N5DKJ🗼